[#5395] Add video_url input to project Metadata, validate YouTube URLs
diff --git a/Allura/allura/ext/admin/admin_main.py b/Allura/allura/ext/admin/admin_main.py
index 34641b9..2655dd6 100644
--- a/Allura/allura/ext/admin/admin_main.py
+++ b/Allura/allura/ext/admin/admin_main.py
@@ -346,6 +346,7 @@
                icon=None,
                category=None,
                external_homepage='',
+               video_url='',
                support_page='',
                support_page_url='',
                twitter_handle='',
@@ -407,6 +408,10 @@
             M.AuditLog.log('change external home page to %s',
                            external_homepage)
             c.project.external_homepage = external_homepage
+        if video_url != c.project.video_url:
+            h.log_action(log, 'change video url').info('')
+            M.AuditLog.log('change video url to %s', video_url)
+            c.project.video_url = video_url
         if support_page != c.project.support_page:
             h.log_action(log, 'change project support page').info('')
             M.AuditLog.log('change project support page to %s', support_page)
diff --git a/Allura/allura/ext/admin/templates/admin_widgets/metadata_admin.html b/Allura/allura/ext/admin/templates/admin_widgets/metadata_admin.html
index a1a20ab..16b303b 100644
--- a/Allura/allura/ext/admin/templates/admin_widgets/metadata_admin.html
+++ b/Allura/allura/ext/admin/templates/admin_widgets/metadata_admin.html
@@ -32,6 +32,10 @@
     <br>
     {{widget.display_field(widget.fields.external_homepage) }}
 
+    {{ widget.display_label(widget.fields.video_url) }}
+    <br>
+    {{widget.display_field(widget.fields.video_url) }}
+
     {{ widget.display_label(widget.fields.summary) }}
     <br>
     {{widget.display_field(widget.fields.summary) }}
diff --git a/Allura/allura/ext/admin/widgets.py b/Allura/allura/ext/admin/widgets.py
index 805fa41..7a78ee4 100644
--- a/Allura/allura/ext/admin/widgets.py
+++ b/Allura/allura/ext/admin/widgets.py
@@ -181,6 +181,8 @@
         icon = ew.FileField(label='Icon')
         external_homepage = ew.InputField(field_type="text", label='Homepage',
                                           validator=fev.URL(add_http=True))
+        video_url = ew.InputField(field_type="text", label="Video (YouTube)",
+                                  validator=V.YouTubeConverter())
         support_page = ew.InputField(field_type="text", label='Support Page')
         support_page_url = ew.InputField(
             field_type="text", label='Support Page URL',
diff --git a/Allura/allura/lib/validators.py b/Allura/allura/lib/validators.py
index 1c0acfd..f23a951 100644
--- a/Allura/allura/lib/validators.py
+++ b/Allura/allura/lib/validators.py
@@ -16,6 +16,7 @@
 #       under the License.
 
 import json
+import re
 from bson import ObjectId
 import formencode as fe
 from formencode import validators as fev
@@ -322,6 +323,27 @@
         return conv_value
 
 
+class YouTubeConverter(fev.FancyValidator):
+    """Takes a given YouTube URL. Ensures that the video_id
+    is contained in the URL. Returns a clean URL to use for iframe embedding.
+
+    REGEX: http://stackoverflow.com/a/10315969/25690
+    """
+
+    REGEX = ('^(?:https?:\/\/)?(?:www\.)?'+
+             '(?:youtu\.be\/|youtube\.com\/(?:embed\/|v\/|watch\?v=|watch\?.+&v=))'+
+             '((\w|-){11})(?:\S+)?$')
+
+    def _to_python(self, value, state):
+        match = re.match(YouTubeConverter.REGEX, value)
+        if match:
+            video_id = match.group(1)
+            return 'www.youtube.com/embed/{}?rel=0'.format(video_id)
+        else:
+            raise fe.Invalid(
+                "The URL does not appear to be a valid YouTube video.",
+                value, state)
+
 def convertDate(datestring):
     formats = ['%Y-%m-%d', '%Y.%m.%d', '%Y/%m/%d', '%Y\%m\%d', '%Y %m %d',
                '%d-%m-%Y', '%d.%m.%Y', '%d/%m/%Y', '%d\%m\%Y', '%d %m %Y']
diff --git a/Allura/allura/model/project.py b/Allura/allura/model/project.py
index eaec993..21b4792 100644
--- a/Allura/allura/model/project.py
+++ b/Allura/allura/model/project.py
@@ -188,6 +188,7 @@
     description_cache = FieldProperty(MarkdownCache)
     homepage_title = FieldProperty(str, if_missing='')
     external_homepage = FieldProperty(str, if_missing='')
+    video_url = FieldProperty(str, if_missing='')
     support_page = FieldProperty(str, if_missing='')
     support_page_url = FieldProperty(str, if_missing='')
     socialnetworks = FieldProperty([dict(socialnetwork=str, accounturl=str)])
@@ -933,6 +934,7 @@
             short_description=self.short_description,
             summary=self.summary,
             external_homepage=self.external_homepage,
+            video_url=self.video_url,
             socialnetworks=[dict(n) for n in self.socialnetworks],
             status=self.removal or 'active',
             moved_to_url=self.moved_to_url,