From 3821e64c930f389752b795376867b44bd5fd8be4 Mon Sep 17 00:00:00 2001
From: Pierre-Yves Chibon <pingou@pingoured.fr>
Date: Oct 05 2018 18:46:41 +0000
Subject: [PATCH 1/2] Add option to allow any authenticated user to edit meta-data on tickets


Fixes https://pagure.io/pagure/issue/3500

Signed-off-by: Pierre-Yves Chibon <pingou@pingoured.fr>

---

diff --git a/pagure/api/issue.py b/pagure/api/issue.py
index 3832bae..f09b2a5 100644
--- a/pagure/api/issue.py
+++ b/pagure/api/issue.py
@@ -136,7 +136,7 @@ def _check_private_issue_access(issue):
         )
 
 
-def _check_ticket_access(issue, assignee=False):
+def _check_ticket_access(issue, assignee=False, open_access=False):
     """Check if user can access issue. Must be repo committer
     or author to see private issues.
     :param issue: issue object
@@ -146,8 +146,11 @@ def _check_ticket_access(issue, assignee=False):
     """
     # Private tickets require commit access
     _check_private_issue_access(issue)
-    # Public tickets require ticket access
-    error = not is_repo_user(issue.project)
+
+    error = False
+    if not open_access:
+        # Public tickets require ticket access
+        error = not is_repo_user(issue.project)
 
     if assignee:
         if (
@@ -824,7 +827,8 @@ def api_change_status_issue(repo, issueid, username=None, namespace=None):
     _check_token(repo, project_token=False)
 
     issue = _get_issue(repo, issueid)
-    _check_ticket_access(issue, assignee=True)
+    open_access = repo.settings.get("open_metadata_access_to_all", False)
+    _check_ticket_access(issue, assignee=True, open_access=open_access)
 
     status = pagure.lib.get_issue_statuses(flask.g.session)
     form = pagure.forms.StatusForm(
@@ -938,7 +942,8 @@ def api_change_milestone_issue(repo, issueid, username=None, namespace=None):
     _check_token(repo)
 
     issue = _get_issue(repo, issueid)
-    _check_ticket_access(issue)
+    open_access = repo.settings.get("open_metadata_access_to_all", False)
+    _check_ticket_access(issue, open_access=open_access)
 
     form = pagure.forms.MilestoneForm(
         milestones=repo.milestones.keys(), csrf_enabled=False
@@ -1125,7 +1130,8 @@ def api_assign_issue(repo, issueid, username=None, namespace=None):
     _check_token(repo)
 
     issue = _get_issue(repo, issueid)
-    _check_ticket_access(issue, assignee=True)
+    open_access = repo.settings.get("open_metadata_access_to_all", False)
+    _check_ticket_access(issue, assignee=True, open_access=open_access)
 
     form = pagure.forms.AssignIssueForm(csrf_enabled=False)
     if form.validate_on_submit():
@@ -1312,7 +1318,8 @@ def api_update_custom_field(
     _check_token(repo)
 
     issue = _get_issue(repo, issueid)
-    _check_ticket_access(issue)
+    open_access = repo.settings.get("open_metadata_access_to_all", False)
+    _check_ticket_access(issue, open_access=open_access)
 
     fields = {k.name: k for k in repo.issue_keys}
     if field not in fields:
@@ -1428,7 +1435,8 @@ def api_update_custom_fields(repo, issueid, username=None, namespace=None):
     _check_token(repo)
 
     issue = _get_issue(repo, issueid)
-    _check_ticket_access(issue)
+    open_access = repo.settings.get("open_metadata_access_to_all", False)
+    _check_ticket_access(issue, open_access=open_access)
 
     fields = get_request_data()
 
diff --git a/pagure/lib/model.py b/pagure/lib/model.py
index 02da8b4..e284a26 100644
--- a/pagure/lib/model.py
+++ b/pagure/lib/model.py
@@ -630,6 +630,7 @@ class Project(BASE):
             "notify_on_commit_flag": False,
             "issue_tracker_read_only": False,
             "disable_non_fast-forward_merges": False,
+            "open_metadata_access_to_all": False,
         }
 
         if self._settings:
diff --git a/pagure/templates/issue.html b/pagure/templates/issue.html
index 55f135d..cd40d61 100644
--- a/pagure/templates/issue.html
+++ b/pagure/templates/issue.html
@@ -18,7 +18,7 @@
 {% block repo %}
 <div class="d-flex align-items-start">
     <h4 class="ml-1">
-        {% if g.authenticated and g.repo_user %}
+        {% if g.authenticated and (g.repo_user or open_access) %}
         <form action="{{ url_for('ui_ns.update_issue', username=username,
         namespace=repo.namespace, repo=repo.name, issueid=issueid)
         }}" method="post" class="hidden" id="changestatusform">
@@ -34,57 +34,57 @@
         {{form.csrf_token}}
         {% if repo.issue_keys %}
           {% for field in repo.issue_keys %}
-                  {% if field.key_type == 'list' %}
-                    <select class="form-control"
-                        name="{{ field.name }}"
-                        id="{{ field.name | replace(' ', '_') }}">
-                      <option value="None">None</option>
-                      {% for item in field.data or [] %}
-                        <option value="{{item}}" {% if field.name in knowns_keys and item == knowns_keys[field.name].value %} selected {% endif %}>
-                          {{ item }}
-                        </option>
-                      {% endfor %}
-                    </select>
-                  {% else %}
-                    <input
-                      {%- if field.key_type == 'boolean' %} type="checkbox" {% endif %}
-                      class="form-control" name="{{ field.name }}" id="{{ field.name }}"
-                      {%- if field.name in knowns_keys %}
-                        {% if field.key_type == 'boolean'%}
-                          {% if knowns_keys[field.name].value in ['true', 'on', '1'] %}checked{% endif %}
-                        {% else %} value="{{ knowns_keys[field.name].value }}"
-                        {% endif %}
-                      {%- endif -%} />
+            {% if field.key_type == 'list' %}
+              <select class="form-control"
+                  name="{{ field.name }}"
+                  id="{{ field.name | replace(' ', '_') }}">
+                <option value="None">None</option>
+                {% for item in field.data or [] %}
+                  <option value="{{item}}" {% if field.name in knowns_keys and item == knowns_keys[field.name].value %} selected {% endif %}>
+                    {{ item }}
+                  </option>
+                {% endfor %}
+              </select>
+            {% else %}
+              <input
+                {%- if field.key_type == 'boolean' %} type="checkbox" {% endif %}
+                class="form-control" name="{{ field.name }}" id="{{ field.name }}"
+                {%- if field.name in knowns_keys %}
+                  {% if field.key_type == 'boolean'%}
+                    {% if knowns_keys[field.name].value in ['true', 'on', '1'] %}checked{% endif %}
+                  {% else %} value="{{ knowns_keys[field.name].value }}"
                   {% endif %}
-            {% endfor %}
+                {%- endif -%} />
+            {% endif %}
+          {% endfor %}
         {% endif %}
         </form>
         {% endif %}
         <div>
-            {% if issue.private %}
-              <span title="Private ticket" class="text-danger fa fa-fw fa-lock"></span>
-            {% endif %}
-            {% if issue.status == 'Open' %}
-              <span class="fa fa-fw text-success fa-exclamation-circle pt-1"></span>
-              <span class="text-success font-weight-bold">#{{issue.id}}</span>
-            {% elif issue.status == 'Closed' %}
-              <span class="fa fa-fw text-danger fa-exclamation-circle pt-1"></span>
-              <span class="text-danger font-weight-bold">#{{issue.id}}</span>
-            {% endif %}
-            <span class="font-weight-bold">
-                  {{ issue.title | noJS(ignore="img") | safe}}
-            </span>
-            {% if g.repo_committer or (
-              g.fas_user and g.fas_user.username == issue.user.username) %}
-            <a class="btn btn-outline-secondary btn-sm border-0" href="{{
-                url_for('ui_ns.edit_issue',
-                        repo=repo.name,
-                        username=username,
-                        namespace=repo.namespace,
-                        issueid=issueid)
-                }}" title="Edit this issue">
-            <i class="fa fa-pencil"></i></a>
-            {% endif %}
+          {% if issue.private %}
+            <span title="Private ticket" class="text-danger fa fa-fw fa-lock"></span>
+          {% endif %}
+          {% if issue.status == 'Open' %}
+            <span class="fa fa-fw text-success fa-exclamation-circle pt-1"></span>
+            <span class="text-success font-weight-bold">#{{issue.id}}</span>
+          {% elif issue.status == 'Closed' %}
+            <span class="fa fa-fw text-danger fa-exclamation-circle pt-1"></span>
+            <span class="text-danger font-weight-bold">#{{issue.id}}</span>
+          {% endif %}
+          <span class="font-weight-bold">
+                {{ issue.title | noJS(ignore="img") | safe}}
+          </span>
+          {% if g.repo_committer or (
+            g.fas_user and g.fas_user.username == issue.user.username) %}
+          <a class="btn btn-outline-secondary btn-sm border-0" href="{{
+              url_for('ui_ns.edit_issue',
+                      repo=repo.name,
+                      username=username,
+                      namespace=repo.namespace,
+                      issueid=issueid)
+              }}" title="Edit this issue">
+          <i class="fa fa-pencil"></i></a>
+          {% endif %}
         </div>
         <div>
           <small>
@@ -119,7 +119,7 @@
     <div class="ml-auto">
         <div class="btn-group">
         <div class="dropdown">
-          {% if g.authenticated and g.repo_user %}
+          {% if g.authenticated and (g.repo_user or open_access) %}
           <a href="javascript:void(0)" class="font-weight-bold btn btn-sm {{'btn-success' if issue.status=='Open' else 'btn-danger'}} dropdown-toggle"
           id="dropdownMenuButton" data-toggle='dropdown' aria-haspopup="true" aria-expanded="false">
           {% else %}
@@ -196,108 +196,108 @@ namespace=repo.namespace, repo=repo.name, issueid=issueid)
         {% if g.authenticated and form and not repo.settings.get('issue_tracker_read_only', False) %}
 
         <div class="card mt-5">
-              <div class="card-header pb-0 pt-1 bg-light">
-                <div class="row">
-                  <div class="col align-self-center">
-                        <span><strong>Add new comment</strong></span>
-                  </div>
-                  <div class="col">
-                          <ul class="nav nav-tabs float-right border-bottom-0">
-                            <li class="nav-item">
-                              <a class="nav-link" id="previewinmarkdown" href="javascript:void(0)">Preview</a>
-                            </li>
-                            <li class="nav-item">
-                              <a class="nav-link active" id="editinmarkdown" href="javascript:void(0)">Edit</a>
-                            </li>
-                          </ul>
-                        {% if repo.quick_replies %}
-                        {% include "quick_reply.html" %}
-                        {% endif %}
-                    </div>
-                </div>
+          <div class="card-header pb-0 pt-1 bg-light">
+            <div class="row">
+              <div class="col align-self-center">
+                    <span><strong>Add new comment</strong></span>
               </div>
-              <div class="card-body">
-                        <textarea class="form-control" rows=8 id="comment" name="comment"
-                        placeholder="Enter your comment here" tabindex=1></textarea>
-                        <div id="preview" class="p-1">
-                        </div>
-                        <div class="mt-2">
-                            <label class="custom-file font-size-09">
-                                <input type="file" id="file-picker" class="custom-file-input" name="file" accept="image/*" multiple tabindex=3>
-                                <label class="custom-file-label" for="file-picker">
-                                  Browse to attach images or drag them into the comment field
-                                </label>
-                            </label>
-                            <div id="progress" class="progress" style="display: none; height:22px">
-                              <div id="progress-bar" class="progress-bar" style="height:22px; line-height: 2em;">0%</div>
-                            </div>
-                        </div>
+              <div class="col">
+                      <ul class="nav nav-tabs float-right border-bottom-0">
+                        <li class="nav-item">
+                          <a class="nav-link" id="previewinmarkdown" href="javascript:void(0)">Preview</a>
+                        </li>
+                        <li class="nav-item">
+                          <a class="nav-link active" id="editinmarkdown" href="javascript:void(0)">Edit</a>
+                        </li>
+                      </ul>
+                    {% if repo.quick_replies %}
+                    {% include "quick_reply.html" %}
+                    {% endif %}
                 </div>
-                <div class="card-footer bg-light">
-                  <div class="d-flex align-items-center">
-                    <small>Comments use <a href="https://docs.pagure.org/pagure/usage/markdown.html"
-                       target="_blank" rel="noopener noreferrer" class="notblue">Markdown Syntax</a></small>
-                    <div class="ml-auto">
-                      <div class="btn-group">
-                        {% if g.authenticated and g.repo_user %}
-                        {% if issue.status == 'Open' %}
-                          {% if repo.close_status %}
-                          <div class="btn-group">
-                          <a href="javascript:void(0)" class="btn btn-outline-primary dropdown-toggle"
-                          id="dropdownMenuButton" data-toggle='dropdown' aria-haspopup="true" aria-expanded="false" tabindex=3>
-                            Comment &amp; Close
-                          </a>
-                          <div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuButton">
-                            <h6 class="dropdown-header">Close issue as:</h6>
-                            {% for close_status in repo.close_status %}
-                              <a class="dropdown-item comment_and_close_action" href="javascript:void(0)" data-value="{{close_status}}">{{close_status}}</a>
-                            {% endfor %}
-                          </div>
-                          </div>
-                          {% else %}
-                            <a class="btn btn-outline-primary comment_and_close_action" data-value="" href="javascript:void(0)">
-                              Comment &amp; Close
-                            </a>
-                          {% endif %}
-                      {% else %}
-                      <a class="btn btn-outline-primary comment_and_close_action" data-value="" href="javascript:void(0)">
-                          Comment &amp; Reopen
-                        </a>
-                      {% endif %}
-                      {% endif %}
-                      <input type="submit" class="btn btn-primary"
-                      value="Comment" tabindex=2 />
-                      </div>
+            </div>
+          </div>
+          <div class="card-body">
+            <textarea class="form-control" rows=8 id="comment" name="comment"
+              placeholder="Enter your comment here" tabindex=1></textarea>
+            <div id="preview" class="p-1">
+            </div>
+            <div class="mt-2">
+                <label class="custom-file font-size-09">
+                    <input type="file" id="file-picker" class="custom-file-input" name="file" accept="image/*" multiple tabindex=3>
+                    <label class="custom-file-label" for="file-picker">
+                      Browse to attach images or drag them into the comment field
+                    </label>
+                </label>
+                <div id="progress" class="progress" style="display: none; height:22px">
+                  <div id="progress-bar" class="progress-bar" style="height:22px; line-height: 2em;">0%</div>
+                </div>
+            </div>
+          </div>
+          <div class="card-footer bg-light">
+            <div class="d-flex align-items-center">
+              <small>Comments use <a href="https://docs.pagure.org/pagure/usage/markdown.html"
+                 target="_blank" rel="noopener noreferrer" class="notblue">Markdown Syntax</a></small>
+              <div class="ml-auto">
+                <div class="btn-group">
+                  {% if g.authenticated and g.repo_user %}
+                  {% if issue.status == 'Open' %}
+                    {% if repo.close_status %}
+                    <div class="btn-group">
+                    <a href="javascript:void(0)" class="btn btn-outline-primary dropdown-toggle"
+                    id="dropdownMenuButton" data-toggle='dropdown' aria-haspopup="true" aria-expanded="false" tabindex=3>
+                      Comment &amp; Close
+                    </a>
+                    <div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuButton">
+                      <h6 class="dropdown-header">Close issue as:</h6>
+                      {% for close_status in repo.close_status %}
+                        <a class="dropdown-item comment_and_close_action" href="javascript:void(0)" data-value="{{close_status}}">{{close_status}}</a>
+                      {% endfor %}
                     </div>
-                  </div>
+                    </div>
+                    {% else %}
+                      <a class="btn btn-outline-primary comment_and_close_action" data-value="" href="javascript:void(0)">
+                        Comment &amp; Close
+                      </a>
+                    {% endif %}
+                {% else %}
+                <a class="btn btn-outline-primary comment_and_close_action" data-value="" href="javascript:void(0)">
+                    Comment &amp; Reopen
+                  </a>
+                {% endif %}
+                {% endif %}
+                <input type="submit" class="btn btn-primary"
+                value="Comment" tabindex=2 />
                 </div>
+              </div>
+            </div>
           </div>
-        {% elif g.authenticated and form and repo.settings.get('issue_tracker_read_only', False) %}
-          <p>
-            This issue tracker is read-only.
-          </p>
-        {% else %}
-          <p>
-            <a href="{{ url_for('auth_login', next=request.url) }}">Login</a>
-            to comment on this ticket.
-          </p>
-        {% endif %}
+        </div>
+      {% elif g.authenticated and form and repo.settings.get('issue_tracker_read_only', False) %}
+        <p>
+          This issue tracker is read-only.
+        </p>
+      {% else %}
+        <p>
+          <a href="{{ url_for('auth_login', next=request.url) }}">Login</a>
+          to comment on this ticket.
+        </p>
+      {% endif %}
 
   </div>
 
   <div class="col-md-4">
     <div>
       <div class="mb-4">
-          <h5 class="d-flex align-items-center font-weight-bold border-bottom">
-              <div class="py-2 text-uppercase font-size-09">Metadata</div>
-              {% if g.authenticated and (g.repo_user or g.fas_user.username == issue.user.user)
-                and not repo.settings.get('issue_tracker_read_only', False) %}
-              <div class="ml-auto">
-              <a class="btn btn-outline-primary border-0 btn-sm issue-metadata-display editmetadatatoggle" href="javascript:void(0)" style="display: inline-block;"><i class="fa fa-fw fa-pencil"></i></a>
-              <a class="btn btn-outline-secondary border-0 btn-sm issue-metadata-form hidden editmetadatatoggle" href="javascript:void(0)" style="display: none;"><i class="fa fa-fw fa-times"></i></a>
-              </div>
-              {% endif %}
-            </h5>
+        <h5 class="d-flex align-items-center font-weight-bold border-bottom">
+            <div class="py-2 text-uppercase font-size-09">Metadata</div>
+            {% if g.authenticated and (g.repo_user or g.fas_user.username == issue.user.user or open_access)
+              and not repo.settings.get('issue_tracker_read_only', False) %}
+            <div class="ml-auto">
+            <a class="btn btn-outline-primary border-0 btn-sm issue-metadata-display editmetadatatoggle" href="javascript:void(0)" style="display: inline-block;"><i class="fa fa-fw fa-pencil"></i></a>
+            <a class="btn btn-outline-secondary border-0 btn-sm issue-metadata-form hidden editmetadatatoggle" href="javascript:void(0)" style="display: none;"><i class="fa fa-fw fa-times"></i></a>
+            </div>
+            {% endif %}
+          </h5>
 
           {% if g.authenticated and (g.repo_user or g.fas_user.username == issue.user.user) %}
             <div class="hidden">
@@ -307,52 +307,52 @@ namespace=repo.namespace, repo=repo.name, issueid=issueid)
           {% endif%}
 
           <fieldset class="form-group issue-metadata-display mt-4">
-              <label class="mb-1 pl-1"> <i class="fa fa-fw fa-user-plus"></i> <strong>Assignee</strong></label>
-              <div id="assignee_plain">
-                <div class="ml-2" title="{{ issue.assignee.html_title if issue.assignee else '' }}">
-                    {% if issue.assignee %}
-                      <div class="mt-1">{{issue.assignee.username| avatar(size=24) | safe}}
-                          <a href="{{ url_for(
-                            'ui_ns.view_issues',
-                            repo=repo.name,
-                            username=username,
-                            namespace=repo.namespace,
-                            assignee=issue.assignee.username)
-                            }}" title="{{ issue.assignee.html_title }}">
-                            {{ issue.assignee.username }}
-                          </a>
-                          {% if g.authenticated and (issue.assignee.username == g.fas_user.username) %}
-                          &mdash; <a href="javascript:void(0)" id="drop-btn"
-                              title="drop the assignment of this issue">
-                            Drop
-                          </a>
-                        {% endif %}
-                      </div>
-                    {% else %}
-                      <div class="text-muted">
-                        None
-                        {% if g.authenticated and g.repo_user and issue.status|lower == 'open'
-                          and (not issue.assignee or issue.assignee.username != g.fas_user.username)
-                          and not repo.settings.get('issue_tracker_read_only', False) %}
-                          &mdash; <a href="javascript:void(0)" id="take-btn"
-                          title="assign this issue to you">
-                            Take
-                        </a>
-                        {% endif %}
-                      </div>
+            <label class="mb-1 pl-1"> <i class="fa fa-fw fa-user-plus"></i> <strong>Assignee</strong></label>
+            <div id="assignee_plain">
+              <div class="ml-2" title="{{ issue.assignee.html_title if issue.assignee else '' }}">
+                {% if issue.assignee %}
+                  <div class="mt-1">{{issue.assignee.username| avatar(size=24) | safe}}
+                      <a href="{{ url_for(
+                        'ui_ns.view_issues',
+                        repo=repo.name,
+                        username=username,
+                        namespace=repo.namespace,
+                        assignee=issue.assignee.username)
+                        }}" title="{{ issue.assignee.html_title }}">
+                        {{ issue.assignee.username }}
+                      </a>
+                      {% if g.authenticated and (issue.assignee.username == g.fas_user.username) %}
+                      &mdash; <a href="javascript:void(0)" id="drop-btn"
+                          title="drop the assignment of this issue">
+                        Drop
+                      </a>
                     {% endif %}
                   </div>
+                {% else %}
+                  <div class="text-muted">
+                    None
+                    {% if g.authenticated and (g.repo_user or open_access) and issue.status|lower == 'open'
+                      and (not issue.assignee or issue.assignee.username != g.fas_user.username)
+                      and not repo.settings.get('issue_tracker_read_only', False) %}
+                      &mdash; <a href="javascript:void(0)" id="take-btn"
+                      title="assign this issue to you">
+                        Take
+                    </a>
+                    {% endif %}
+                  </div>
+                {% endif %}
               </div>
-            </fieldset>
+            </div>
+          </fieldset>
 
-            {% if g.authenticated and g.repo_user %}
-            <fieldset class="form-group issue-metadata-form hidden">
-              <label for="assignee"><strong>Assignee</strong></label>
-              <input class="form-control" name="assignee" id="assignee"
-                placeholder="username"
-                value="{{ issue.assignee.username or '' }}" />
-            </fieldset>
-            {% endif%}
+          {% if g.authenticated and (g.repo_user or open_access) %}
+          <fieldset class="form-group issue-metadata-form hidden">
+            <label for="assignee"><strong>Assignee</strong></label>
+            <input class="form-control" name="assignee" id="assignee"
+              placeholder="username"
+              value="{{ issue.assignee.username or '' }}" />
+          </fieldset>
+          {% endif%}
 
           <fieldset class="form-group issue-metadata-display mt-4">
             <label class="mb-1"><i class="fa fa-fw fa-tag"></i> <strong>Tags</strong></label>
@@ -376,7 +376,7 @@ namespace=repo.namespace, repo=repo.name, issueid=issueid)
             {% endif%}
           </fieldset>
 
-          {% if g.authenticated and g.repo_user %}
+          {% if g.authenticated and (g.repo_user or open_access) %}
           <fieldset class="form-group issue-metadata-form hidden">
             <label for="tag"><strong>Tags</strong></label>
              <input id="tag" type="text" placeholder="tag1, tag2" name="tag"
@@ -416,7 +416,7 @@ namespace=repo.namespace, repo=repo.name, issueid=issueid)
             </div>
           </fieldset>
 
-          {% if g.authenticated and g.repo_user %}
+          {% if g.authenticated and (g.repo_user or open_access) %}
             <fieldset class="form-group issue-metadata-form hidden">
               <label for="blocking"><strong>Blocking</strong></label>
               <input class="form-control" id="blocking" type="text"
@@ -438,7 +438,7 @@ namespace=repo.namespace, repo=repo.name, issueid=issueid)
           </div>
           </fieldset>
 
-          {% if g.authenticated and g.repo_user %}
+          {% if g.authenticated and (g.repo_user or open_access) %}
             <fieldset class="form-group issue-metadata-form hidden">
               <label for="depending"><strong>Depending on</strong></label>
               <input class="form-control" id="depending" type="text"
@@ -458,7 +458,7 @@ namespace=repo.namespace, repo=repo.name, issueid=issueid)
             {% endif %}
             </div>
           </fieldset>
-          {% if g.authenticated and g.repo_user %}
+          {% if g.authenticated and (g.repo_user or open_access) %}
             {{ render_bootstrap_field(form.priority,
               formclass="issue-metadata-form hidden") }}
           {% endif%}
@@ -485,16 +485,16 @@ namespace=repo.namespace, repo=repo.name, issueid=issueid)
               {% endif %}
             </div>
           </fieldset>
-          {% if g.authenticated and g.repo_user %}
+          {% if g.authenticated and (g.repo_user or open_access) %}
             {{ render_bootstrap_field(form.milestone,
-              formclass="issue-metadata-form hidden") }}
+                 formclass="issue-metadata-form hidden") }}
           {% endif%}
 
           {% endif %}
 
-          {% if g.authenticated and g.repo_user %}
+          {% if g.authenticated and (g.repo_user or open_access) %}
             {{ render_bootstrap_field(form.private,
-              formclass="issue-metadata-form hidden") }}
+                 formclass="issue-metadata-form hidden") }}
           {% endif%}
 
           {% if repo.issue_keys %}
@@ -516,7 +516,7 @@ namespace=repo.namespace, repo=repo.name, issueid=issueid)
                   {% endif %}
               </div>
             </fieldset>
-            {% if g.authenticated and g.repo_user %}
+            {% if g.authenticated and (g.repo_user or open_access) %}
               <fieldset class="form-group issue-metadata-form hidden">
                 <label for="field"><strong> <i class="fa fa-fw fa-circle-o"></i>{{ field.name }}</strong></label>
                 {% if field.key_type == 'list' %}
@@ -576,32 +576,33 @@ namespace=repo.namespace, repo=repo.name, issueid=issueid)
 
       <div class="mt-3">
         <h5 class="d-flex align-items-center font-weight-bold border-bottom">
-            <div class="py-2 text-uppercase font-size-09">
-              Subscribers
-              <span class="badge badge-secondary badge-pill font-size-09 ml-1" id="subscribers-count">{{subscribers|count}}</span>
-            </div>
-            <div class="ml-auto">
-                <a href="#" class="btn btn-sm btn-link" id="subcribe-btn"
-                {% if g.fas_user.username in subscribers -%}
-                  title="Unsubscribe from this issue">Unsubscribe
-                {%- else -%}
-                  title="Subscribe to this issue">Subscribe
-                {%- endif -%}
-              </a>
-              </div>
-          </h5>
-          {% if subscribers %}
-          <div id="subscribers_list" class="p-2">
-          {% for subscriber in subscribers %}
-              <a href="{{ url_for('ui_ns.view_user', username=subscriber)
-              }}" title="{{ subscriber }}" id="sub-avatar-{{subscriber}}">{{
-                subscriber |avatar(size=30, css_class="pb-1") | safe
-              }}</a>
-          {% endfor %}
+          <div class="py-2 text-uppercase font-size-09">
+            Subscribers
+            <span class="badge badge-secondary badge-pill font-size-09 ml-1" id="subscribers-count">{{subscribers|count}}</span>
           </div>
-          {% else %}
-          <div class="text-center text-muted pt-2">No Subscribers</div>
-          {% endif %}
+          <div class="ml-auto">
+              <a href="#" class="btn btn-sm btn-link" id="subcribe-btn"
+              {% if g.fas_user.username in subscribers -%}
+                title="Unsubscribe from this issue">Unsubscribe
+              {%- else -%}
+                title="Subscribe to this issue">Subscribe
+              {%- endif -%}
+            </a>
+            </div>
+        </h5>
+
+        {% if subscribers %}
+        <div id="subscribers_list" class="p-2">
+        {% for subscriber in subscribers %}
+            <a href="{{ url_for('ui_ns.view_user', username=subscriber)
+            }}" title="{{ subscriber }}" id="sub-avatar-{{subscriber}}">{{
+              subscriber |avatar(size=30, css_class="pb-1") | safe
+            }}</a>
+        {% endfor %}
+        </div>
+        {% else %}
+        <div class="text-center text-muted pt-2">No Subscribers</div>
+        {% endif %}
 
       </div>
     {% endif %}
@@ -609,32 +610,31 @@ namespace=repo.namespace, repo=repo.name, issueid=issueid)
 
     {% if issue.related_prs %}
       <div class="mt-3">
-          <h5 class="d-flex align-items-center font-weight-bold border-bottom">
-              <div class="py-2 text-uppercase font-size-09">
-                Related Pull Requests
-              </div>
-            </h5>
-          <div id="pr_list">
-            <ul class="list-unstyled">
-              {% for pr in issue.related_prs %}
-              <li>
-                <a class="badge badge-secondary" href="{{ url_for(
-                    'ui_ns.request_pull',
-                    repo=pr.project.name,
-                    username=pr.project.user.user if pr.project.is_fork else none,
-                    namespace=pr.project.namespace,
-                    requestid=pr.id) }}">#{{pr.id}}</a>
-                {{ pr.status if pr.status != 'Open' else 'Last updated'
-                  }} {{ pr.last_updated | humanize }}
-              </li>
-              {% endfor %}
-            </ul>
+        <h5 class="d-flex align-items-center font-weight-bold border-bottom">
+          <div class="py-2 text-uppercase font-size-09">
+            Related Pull Requests
           </div>
+        </h5>
+        <div id="pr_list">
+          <ul class="list-unstyled">
+            {% for pr in issue.related_prs %}
+            <li>
+              <a class="badge badge-secondary" href="{{ url_for(
+                  'ui_ns.request_pull',
+                  repo=pr.project.name,
+                  username=pr.project.user.user if pr.project.is_fork else none,
+                  namespace=pr.project.namespace,
+                  requestid=pr.id) }}">#{{pr.id}}</a>
+              {{ pr.status if pr.status != 'Open' else 'Last updated'
+                }} {{ pr.last_updated | humanize }}
+            </li>
+            {% endfor %}
+          </ul>
+        </div>
       </div>
     {% endif %}
   </div>
 
-
   </div>
 </form>
 
@@ -1024,7 +1024,7 @@ function try_async_comment(form) {
 
 
 <script type="text/javascript">
-{% if g.authenticated and g.repo_user %}
+{% if g.authenticated and (g.repo_user or open_access) %}
 function take_issue(){
   var _url = "{{ url_for('api_ns.api_assign_issue',
             repo=repo.name, namespace=repo.namespace, username=username,
@@ -1069,7 +1069,7 @@ function drop_issue(){
 {% endif %}
 
 function setup_btn_take_drop(){
-  {% if g.authenticated and g.repo_user %}
+  {% if g.authenticated and (g.repo_user or open_access) %}
   $("#take-btn").click(take_issue)
   {% endif %}
   {% if g.authenticated and (
@@ -1257,6 +1257,7 @@ $( "#previewinmarkdown" ).click(
 
   {% if g.authenticated and (
     g.repo_user
+    or open_access
     or issue.user.user == g.fas_user.username
     or issue.assignee.user == g.fas_user.username) %}
   setup_btn_take_drop();
diff --git a/pagure/templates/new_issue.html b/pagure/templates/new_issue.html
index 4b2f4e6..fbfba50 100644
--- a/pagure/templates/new_issue.html
+++ b/pagure/templates/new_issue.html
@@ -160,7 +160,7 @@
       </div>
   </div>
 
-  {% if g.repo_user and not type or type == 'new' %}
+  {% if (g.repo_user or open_access) and not type or type == 'new' %}
   <div class="col-md-4">
     <div class="mb-4">
         <h5 class="d-flex align-items-center font-weight-bold border-bottom mb-4">
@@ -291,7 +291,7 @@ $(document).ready(function() {
     });
   });
 
-  {% if g.repo_user %}
+  {% if (g.repo_user or open_access) %}
   var available_tags = [];
   {% for tog in tag_list %}
     available_tags.push("{{ tog.tag }}");
diff --git a/pagure/ui/issues.py b/pagure/ui/issues.py
index e1bef85..d595a46 100644
--- a/pagure/ui/issues.py
+++ b/pagure/ui/issues.py
@@ -127,6 +127,10 @@ def update_issue(repo, issueid, username=None, namespace=None):
         close_status=repo.close_status,
     )
 
+    is_author = flask.g.fas_user.username == issue.user.user
+    is_contributor = flask.g.repo_user
+    is_open_access = repo.settings.get("open_metadata_access_to_all", False)
+
     if form.validate_on_submit():
         if flask.request.form.get("drop_comment"):
             commentid = flask.request.form.get("drop_comment")
@@ -138,8 +142,7 @@ def update_issue(repo, issueid, username=None, namespace=None):
                 flask.abort(404, "Comment not found")
 
             if (
-                flask.g.fas_user.username != comment.user.username
-                or comment.parent.status != "Open"
+                not is_author or comment.parent.status != "Open"
             ) and not flask.g.repo_committer:
                 flask.abort(
                     403,
@@ -219,10 +222,7 @@ def update_issue(repo, issueid, username=None, namespace=None):
             # The status field can be updated by both the admin and the
             # person who opened the ticket.
             # Update status
-            if (
-                flask.g.repo_user
-                or flask.g.fas_user.username == issue.user.user
-            ):
+            if is_contributor or is_author:
                 if new_status in status:
                     msgs = pagure.lib.edit_issue(
                         flask.g.session,
@@ -240,7 +240,7 @@ def update_issue(repo, issueid, username=None, namespace=None):
             # All the other meta-data can be changed only by admins
             # while other field will be missing for non-admin and thus
             # reset if we let them
-            if flask.g.repo_user:
+            if is_contributor or is_open_access:
                 # Adjust (add/remove) tags
                 msgs = pagure.lib.update_tags(
                     flask.g.session,
@@ -870,6 +870,7 @@ def new_issue(repo, username=None, namespace=None):
     """
     template = flask.request.args.get("template") or "default"
     repo = flask.g.repo
+    open_access = repo.settings.get("open_metadata_access_to_all", False)
 
     milestones = []
     for m in repo.milestones_keys or repo.milestones:
@@ -906,7 +907,7 @@ def new_issue(repo, username=None, namespace=None):
             assignee = None
             milestone = None
             tags = None
-            if flask.g.repo_user:
+            if flask.g.repo_user or open_access:
                 assignee = (
                     flask.request.form.get("assignee", "").strip() or None
                 )
@@ -1038,6 +1039,7 @@ def new_issue(repo, username=None, namespace=None):
         types=types,
         default=default,
         tag_list=tag_list,
+        open_access=open_access,
     )
 
 
@@ -1097,6 +1099,8 @@ def view_issue(repo, issueid, username=None, namespace=None):
     for key in issue.other_fields:
         knowns_keys[key.key.name] = key
 
+    open_access = repo.settings.get("open_metadata_access_to_all", False)
+
     return flask.render_template(
         "issue.html",
         select="issues",
@@ -1107,6 +1111,7 @@ def view_issue(repo, issueid, username=None, namespace=None):
         issueid=issueid,
         form=form,
         knowns_keys=knowns_keys,
+        open_access=open_access,
         subscribers=pagure.lib.get_watch_list(flask.g.session, issue),
         attachments=issue.attachments,
     )
diff --git a/tests/test_pagure_flask_ui_issues.py b/tests/test_pagure_flask_ui_issues.py
index 6746b3a..2be424f 100644
--- a/tests/test_pagure_flask_ui_issues.py
+++ b/tests/test_pagure_flask_ui_issues.py
@@ -546,7 +546,7 @@ class PagureFlaskIssuestests(tests.Modeltests):
                 'title="comma separated list of tags"\n                '
                 'value="tag2" />', output_text)
             self.assertIn(
-                'placeholder="username"\n                value="foo" />\n',
+                'placeholder="username"\n              value="foo" />\n',
                 output_text)
             self.assertIn(
                 'href="/test/roadmap/v2.0/"',
@@ -1113,7 +1113,7 @@ class PagureFlaskIssuestests(tests.Modeltests):
             output_text)
         self.assertIn(
             '<a href="/login/?next=http%3A%2F%2Flocalhost%2Ftest%2Fissue%2F1">'
-            'Login</a>\n            to comment on this ticket.',
+            'Login</a>\n          to comment on this ticket.',
             output_text)
 
     @patch('pagure.lib.git.update_git')
@@ -1155,7 +1155,7 @@ class PagureFlaskIssuestests(tests.Modeltests):
             output_text)
         self.assertIn(
             '<a href="/login/?next=http%3A%2F%2Flocalhost%2Ftest%2Fissue%2F1">'
-            'Login</a>\n            to comment on this ticket.',
+            'Login</a>\n          to comment on this ticket.',
             output_text)
 
         user = tests.FakeUser()
@@ -1286,7 +1286,7 @@ class PagureFlaskIssuestests(tests.Modeltests):
             output_text)
         self.assertTrue(
             '<a href="/login/?next=http%3A%2F%2Flocalhost%2Ftest%2Fissue%2F1">'
-            'Login</a>\n            to comment on this ticket.'
+            'Login</a>\n          to comment on this ticket.'
             in output_text)
 
         # Create issues to play with
@@ -2607,7 +2607,7 @@ class PagureFlaskIssuestests(tests.Modeltests):
             output_text = output.get_data(as_text=True)
             self.assertIn(
                 ' <span class="fa fa-fw text-success fa-exclamation-circle pt-1"></span>\n'
-                '              <span class="text-success font-weight-bold">#1</span>\n ',
+                '            <span class="text-success font-weight-bold">#1</span>\n ',
                 output_text)
             self.assertEqual(output_text.count(
                 '<option selected value="Open">Open</option>'), 1)
@@ -2670,7 +2670,7 @@ class PagureFlaskIssuestests(tests.Modeltests):
             output_text = output.get_data(as_text=True)
             self.assertIn(
                 ' <span class="fa fa-fw text-success fa-exclamation-circle pt-1"></span>\n'
-                '              <span class="text-success font-weight-bold">#1</span>\n ',
+                '            <span class="text-success font-weight-bold">#1</span>\n ',
                 output_text)
             self.assertEqual(output_text.count(
                 '<option selected value="Open">Open</option>'), 1)
diff --git a/tests/test_pagure_flask_ui_issues_acl_checks.py b/tests/test_pagure_flask_ui_issues_acl_checks.py
index 0aeeb8b..a77ec63 100644
--- a/tests/test_pagure_flask_ui_issues_acl_checks.py
+++ b/tests/test_pagure_flask_ui_issues_acl_checks.py
@@ -116,7 +116,7 @@ class PagureFlaskIssuesACLtests(tests.Modeltests):
             output.get_data(as_text=True))
         self.assertTrue(
             '<a href="/login/?next=http%3A%2F%2Flocalhost%2Ftest%2Fissue%2F1">'
-            'Login</a>\n            to comment on this ticket.'
+            'Login</a>\n          to comment on this ticket.'
             in output.get_data(as_text=True))
 
         user = tests.FakeUser()
@@ -372,7 +372,7 @@ class PagureFlaskIssuesACLtests(tests.Modeltests):
             output_text)
         self.assertIn(
             '<a href="/login/?next=http%3A%2F%2Flocalhost%2Ftest%2Fissue%2F1">'
-            'Login</a>\n            to comment on this ticket.',
+            'Login</a>\n          to comment on this ticket.',
             output_text)
 
         user = tests.FakeUser()
@@ -879,7 +879,7 @@ class PagureFlaskIssuesACLtests(tests.Modeltests):
             output.get_data(as_text=True))
         self.assertTrue(
             '<a href="/login/?next=http%3A%2F%2Flocalhost%2Ftest%2Fissue%2F1">'
-            'Login</a>\n            to comment on this ticket.'
+            'Login</a>\n          to comment on this ticket.'
             in output.get_data(as_text=True))
 
         user = tests.FakeUser()
diff --git a/tests/test_pagure_flask_ui_issues_open_access.py b/tests/test_pagure_flask_ui_issues_open_access.py
new file mode 100644
index 0000000..76c6d87
--- /dev/null
+++ b/tests/test_pagure_flask_ui_issues_open_access.py
@@ -0,0 +1,1306 @@
+# -*- coding: utf-8 -*-
+
+"""
+ (c) 2015-2018 - Copyright Red Hat Inc
+
+ Authors:
+   Pierre-Yves Chibon <pingou@pingoured.fr>
+
+"""
+
+from __future__ import unicode_literals
+
+__requires__ = ['SQLAlchemy >= 0.8']
+import pkg_resources
+
+from unittest.case import SkipTest
+import json
+import unittest
+import shutil
+import sys
+import os
+try:
+    import pyclamd
+except ImportError:
+    pyclamd = None
+import six
+import tempfile
+import re
+from datetime import datetime, timedelta
+from six.moves.urllib.parse import urlparse, parse_qs
+
+import pygit2
+from bs4 import BeautifulSoup
+from mock import patch, MagicMock
+
+sys.path.insert(0, os.path.join(os.path.dirname(
+    os.path.abspath(__file__)), '..'))
+
+import pagure
+import pagure.lib
+import tests
+
+
+class PagureFlaskIssuesOpenAccesstests(tests.Modeltests):
+    """ Tests for flask issues controller of pagure """
+
+    def setUp(self):
+        """ Set up the environnment, ran before every tests. """
+        super(PagureFlaskIssuesOpenAccesstests, self).setUp()
+
+        tests.create_projects(self.session)
+        tests.create_projects_git(
+            os.path.join(self.path, 'repos'), bare=True)
+        tests.create_projects_git(
+            os.path.join(self.path, 'tickets'), bare=True)
+
+        repo = pagure.lib.get_authorized_project(self.session, 'test')
+        settings = repo.settings
+        settings['open_metadata_access_to_all'] = True
+        repo.settings = settings
+        repo.milestones = {'v1.0': '', 'v2.0': 'Tomorrow!'}
+        self.session.add(repo)
+        self.session.commit()
+
+    @patch('pagure.lib.git.update_git', MagicMock(return_value=True))
+    @patch('pagure.lib.notify.send_email', MagicMock(return_value=True))
+    def test_new_issue_with_metadata(self):
+        """ Test the new_issue endpoint when the user has access to the
+        project. """
+
+        user = tests.FakeUser()
+        user.username = 'foo'
+        with tests.user_set(self.app.application, user):
+            output = self.app.get('/test/new_issue')
+            self.assertEqual(output.status_code, 200)
+            output_text = output.get_data(as_text=True)
+            self.assertIn(
+                '<h4 class="font-weight-bold mb-4">New Issue</h4>\n',
+                output_text)
+            self.assertIn('<strong>Tags</strong>', output_text)
+            self.assertIn(
+                '<strong>Assignee</strong>', output_text)
+
+            csrf_token = self.get_csrf(output=output)
+
+            data = {
+                    'title': 'Test issue3',
+                    'issue_content': 'We really should improve on this issue\n',
+                    'status': 'Open',
+                    'assignee': 'foo',
+                    'milestone': 'v2.0',
+                    'tag': 'tag2',
+                    'csrf_token': csrf_token,
+                }
+
+            output = self.app.post(
+                '/test/new_issue', data=data, follow_redirects=True)
+
+            self.assertEqual(output.status_code, 200)
+            output_text = output.get_data(as_text=True)
+            self.assertIn(
+                '<title>Issue #1: Test issue3 - test - Pagure</title>',
+                output_text)
+            self.assertIn(
+                '<a class="btn btn-outline-secondary btn-sm border-0" '
+                'href="/test/issue/1/edit" title="Edit this issue">\n',
+                output_text)
+            # Check the metadata
+            self.assertIn(
+                'title="comma separated list of tags"\n                '
+                'value="tag2" />', output_text)
+            self.assertIn(
+                'placeholder="username"\n              value="foo" />\n',
+                output_text)
+            self.assertIn(
+                'href="/test/roadmap/v2.0/"',
+                output_text)
+
+    @patch('pagure.lib.git.update_git', MagicMock(return_value=True))
+    @patch('pagure.lib.notify.send_email', MagicMock(return_value=True))
+    def test_new_issue_with_metadata_not_user(self):
+        """ Test the new_issue endpoint when the user does not have access
+        to the project but still tries to.
+        """
+
+        user = tests.FakeUser()
+        user.username = 'foo'
+        with tests.user_set(self.app.application, user):
+            output = self.app.get('/test/new_issue')
+            self.assertEqual(output.status_code, 200)
+            output_text = output.get_data(as_text=True)
+            self.assertIn(
+                '<h4 class="font-weight-bold mb-4">New Issue</h4>\n',
+                output_text)
+            self.assertIn('<strong>Tags</strong>', output_text)
+            self.assertIn('<strong>Assignee</strong>', output_text)
+
+            csrf_token = self.get_csrf(output=output)
+
+            data = {
+                    'title': 'Test issue3',
+                    'issue_content': 'We really should improve on this issue\n',
+                    'status': 'Open',
+                    'assignee': 'foo',
+                    'milestone': 'v2.0',
+                    'tag': 'tag2',
+                    'csrf_token': csrf_token,
+                }
+
+            output = self.app.post(
+                '/test/new_issue', data=data, follow_redirects=True)
+
+            self.assertEqual(output.status_code, 200)
+            output_text = output.get_data(as_text=True)
+            self.assertIn(
+                '<title>Issue #1: Test issue3 - test - Pagure</title>',
+                output_text)
+            self.assertIn(
+                '<a class="btn btn-outline-secondary btn-sm border-0" '
+                'href="/test/issue/1/edit" title="Edit this issue">\n',
+                output_text)
+            # Check the metadata
+            self.assertIn(
+                'title="comma separated list of tags"\n                '
+                'value="tag2" />', output_text)
+            self.assertIn(
+                'placeholder="username"\n              value="foo" />\n',
+                output_text)
+            self.assertIn(
+                '<div class="ml-2" id="milestone_plain">'
+                '\n              <span>'
+                '\n                <a href="/test/roadmap/v2.0/">'
+                '\n                  v2.0\n', output_text)
+
+    @patch('pagure.lib.git.update_git', MagicMock(return_value=True))
+    @patch('pagure.lib.notify.send_email', MagicMock(return_value=True))
+    def test_view_issue(self):
+        """ Test the view_issue endpoint. """
+        output = self.app.get('/test/issue/1')
+        self.assertEqual(output.status_code, 404)
+
+        # Create issues to play with
+        repo = pagure.lib.get_authorized_project(self.session, 'test')
+        msg = pagure.lib.new_issue(
+            session=self.session,
+            repo=repo,
+            title='Test issue',
+            content='We should work on this',
+            user='pingou',
+        )
+        self.session.commit()
+        self.assertEqual(msg.title, 'Test issue')
+
+        output = self.app.get('/test/issue/1')
+        self.assertEqual(output.status_code, 200)
+        output_text = output.get_data(as_text=True)
+        # Not authentified = No edit
+        self.assertNotIn(
+            '<a class="btn btn-outline-secondary btn-sm border-0" '
+            'href="/test/issue/1/edit" title="Edit this issue">\n',
+            output_text)
+        self.assertIn(
+            '<a href="/login/?next=http%3A%2F%2Flocalhost%2Ftest%2Fissue%2F1">'
+            'Login</a>\n          to comment on this ticket.',
+            output_text)
+
+        user = tests.FakeUser()
+        with tests.user_set(self.app.application, user):
+            output = self.app.get('/test/issue/1')
+            self.assertEqual(output.status_code, 200)
+            output_text = output.get_data(as_text=True)
+            # Not author nor admin = No edit
+            self.assertNotIn(
+                '<a class="btn btn-outline-secondary btn-sm border-0"'
+                 ' href="/test/issue/1/edit" title="Edit this issue">',
+                output_text)
+            self.assertNotIn(
+                '<a class="dropdown-item text-danger" href="javascript:void(0)" id="closeticket"\n'
+                '                title="Delete this ticket">\n',
+                output_text)
+            self.assertFalse(
+                '<a href="/login/">Login</a> to comment on this ticket.'
+                in output_text)
+            # Not author nor admin but open_access = take
+            self.assertIn('function take_issue(){', output_text)
+            self.assertNotIn('function drop_issue(){', output_text)
+            self.assertIn(
+                '<a href="javascript:void(0)" id="take-btn"\n',
+                output_text)
+
+            csrf_token = self.get_csrf(output=output)
+
+        # Create private issue
+        repo = pagure.lib.get_authorized_project(self.session, 'test')
+        msg = pagure.lib.new_issue(
+            session=self.session,
+            repo=repo,
+            title='Test issue',
+            content='We should work on this',
+            user='pingou',
+            private=True,
+        )
+        self.session.commit()
+        self.assertEqual(msg.title, 'Test issue')
+
+        # Not logged in
+        output = self.app.get('/test/issue/2')
+        self.assertEqual(output.status_code, 404)
+
+        # Wrong user
+        user = tests.FakeUser()
+        with tests.user_set(self.app.application, user):
+            output = self.app.get('/test/issue/2')
+            self.assertEqual(output.status_code, 404)
+
+        # another user
+        user.username = 'foo'
+        with tests.user_set(self.app.application, user):
+            output = self.app.get('/test/issue/2')
+            self.assertEqual(output.status_code, 404)
+
+        # Project w/o issue tracker
+        repo = pagure.lib.get_authorized_project(self.session, 'test')
+        repo.settings = {'issue_tracker': False}
+        self.session.add(repo)
+        self.session.commit()
+
+        output = self.app.get('/test/issue/1')
+        self.assertEqual(output.status_code, 404)
+
+    @patch('pagure.lib.git.update_git', MagicMock(return_value=True))
+    @patch('pagure.lib.notify.send_email', MagicMock(return_value=True))
+    def test_view_issue_user_ticket(self):
+        """ Test the view_issue endpoint. """
+
+        output = self.app.get('/test/issue/1')
+        self.assertEqual(output.status_code, 404)
+
+        # Create issues to play with
+        repo = pagure.lib.get_authorized_project(self.session, 'test')
+        msg = pagure.lib.new_issue(
+            session=self.session,
+            repo=repo,
+            title='Test issue',
+            content='We should work on this',
+            user='pingou',
+        )
+        self.session.commit()
+        self.assertEqual(msg.title, 'Test issue')
+
+        output = self.app.get('/test/issue/1')
+        self.assertEqual(output.status_code, 200)
+        output_text = output.get_data(as_text=True)
+        # Not authentified = No edit
+        self.assertNotIn(
+            '<a class="btn btn-outline-secondary btn-sm border-0" '
+            'href="/test/issue/1/edit" title="Edit this issue">\n',
+            output_text)
+        self.assertTrue(
+            '<a href="/login/?next=http%3A%2F%2Flocalhost%2Ftest%2Fissue%2F1">'
+            'Login</a>\n          to comment on this ticket.'
+            in output_text)
+
+        # Create issues to play with
+        repo = pagure.lib.get_authorized_project(self.session, 'test')
+
+        # Add user 'foo' with ticket access on repo
+        msg = pagure.lib.add_user_to_project(
+            self.session,
+            repo,
+            new_user='foo',
+            user='pingou',
+            access='ticket',
+        )
+        self.assertEqual(msg, 'User added')
+        self.session.commit()
+
+        user = tests.FakeUser(username='foo')
+        with tests.user_set(self.app.application, user):
+            output = self.app.get('/test/issue/1')
+            self.assertEqual(output.status_code, 200)
+            output_text = output.get_data(as_text=True)
+            # Not author nor admin = No edit
+            self.assertNotIn(
+                '<a class="btn btn-outline-secondary btn-sm border-0"'
+                 ' href="/test/issue/1/edit" title="Edit this issue">',
+                output_text)
+            self.assertNotIn(
+                '<a class="dropdown-item text-danger" href="javascript:void(0)" id="closeticket"\n'
+                '                title="Delete this ticket">\n',
+                output_text)
+            self.assertFalse(
+                '<a href="/login/">Login</a> to comment on this ticket.'
+                in output_text)
+            # user has ticket = take ok
+            self.assertIn('function take_issue(){', output_text)
+            self.assertIn('function drop_issue(){', output_text)
+            self.assertIn(
+                '<a href="javascript:void(0)" id="take-btn"\n',
+                output_text)
+
+    @patch('pagure.lib.git.update_git', MagicMock(return_value=True))
+    @patch('pagure.lib.notify.send_email', MagicMock(return_value=True))
+    def test_view_issue_custom_field_user_ticket(self):
+        """ Test the view_issue endpoint. """
+        output = self.app.get('/test/issue/1')
+        self.assertEqual(output.status_code, 404)
+
+        # Create issues to play with
+        repo = pagure.lib.get_authorized_project(self.session, 'test')
+        msg = pagure.lib.new_issue(
+            session=self.session,
+            repo=repo,
+            title='Test issue',
+            content='We should work on this',
+            user='pingou',
+        )
+        self.session.commit()
+        self.assertEqual(msg.title, 'Test issue')
+
+        # Add user 'foo' with ticket access on repo
+        repo = pagure.lib.get_authorized_project(self.session, 'test')
+        msg = pagure.lib.add_user_to_project(
+            self.session,
+            repo,
+            new_user='foo',
+            user='pingou',
+            access='ticket',
+        )
+        self.assertEqual(msg, 'User added')
+        self.session.commit()
+
+        # Set some custom fields
+        repo = pagure.lib.get_authorized_project(self.session, 'test')
+        msg = pagure.lib.set_custom_key_fields(
+            self.session,
+            repo,
+            ['bugzilla', 'upstream', 'reviewstatus'],
+            ['link', 'boolean', 'list'],
+            ['unused data for non-list type', '', 'ack, nack ,  needs review'],
+            [None, None, None])
+        self.session.commit()
+        self.assertEqual(msg, 'List of custom fields updated')
+
+        # User with no rights
+        user = tests.FakeUser()
+        with tests.user_set(self.app.application, user):
+            output = self.app.get('/test/issue/1')
+            self.assertEqual(output.status_code, 200)
+            output_text = output.get_data(as_text=True)
+            self.assertNotIn(
+                '<a class="btn btn-outline-secondary btn-sm border-0"'
+                 ' href="/test/issue/1/edit" title="Edit this issue">',
+                output_text)
+            self.assertNotIn(
+                '<a class="dropdown-item text-danger" href="javascript:void(0)" id="closeticket"\n'
+                '                title="Delete this ticket">\n',
+                output_text)
+            # user no ACLs but open_access = take action/button - no drop
+            self.assertIn('function take_issue(){', output_text)
+            self.assertNotIn('function drop_issue(){', output_text)
+            self.assertIn(
+                '<a href="javascript:void(0)" id="take-btn"\n',
+                output_text)
+
+            # user no ACLs = no metadata form
+            self.assertNotIn(
+                '<input                  class="form-control" '
+                'name="bugzilla" id="bugzilla"/>', output_text)
+            self.assertNotIn(
+                '<select class="form-control" name="reviewstatus" '
+                'id="reviewstatus>', output_text)
+            self.assertNotIn(
+                '<input type="checkbox"                   '
+                'class="form-control" name="upstream" id="upstream"/>',
+                output_text)
+
+        user = tests.FakeUser(username='foo')
+        with tests.user_set(self.app.application, user):
+            output = self.app.get('/test/issue/1')
+            self.assertEqual(output.status_code, 200)
+            output_text = output.get_data(as_text=True)
+            self.assertNotIn(
+                '<a class="btn btn-outline-secondary btn-sm border-0"'
+                 ' href="/test/issue/1/edit" title="Edit this issue">',
+                output_text)
+            self.assertNotIn(
+                '<a class="dropdown-item text-danger" href="javascript:void(0)" id="closeticket"\n'
+                '                title="Delete this ticket">\n',
+                output_text)
+            self.assertNotIn(
+                '<a href="/login/">Login</a> to comment on this ticket.',
+                output_text)
+            # user has ticket = take ok
+            self.assertIn('function take_issue(){', output_text)
+            self.assertIn('function drop_issue(){', output_text)
+            self.assertIn(
+                '<a href="javascript:void(0)" id="take-btn"\n',
+                output_text)
+
+            # user has ticket == Sees the metadata
+            self.assertIn(
+                '<input                    class="form-control" '
+                'name="bugzilla" id="bugzilla"/>', output_text)
+            self.assertIn(
+                '<select class="form-control"\n'
+                '                      name="reviewstatus"\n'
+                '                      id="reviewstatus">\n',
+                output_text)
+            self.assertIn(
+                '<input type="checkbox"                     '
+                'class="form-control" name="upstream" id="upstream"/>',
+                output_text)
+
+    @patch('pagure.lib.git.update_git', MagicMock(return_value=True))
+    @patch('pagure.lib.notify.send_email', MagicMock(return_value=True))
+    def test_view_issue_non_ascii_milestone(self):
+        """ Test the view_issue endpoint with non-ascii milestone. """
+        output = self.app.get('/test/issue/1')
+        self.assertEqual(output.status_code, 404)
+
+        stone = 'käpy'
+        # Create issues to play with
+        repo = pagure.lib.get_authorized_project(self.session, 'test')
+        msg = pagure.lib.new_issue(
+            session=self.session,
+            repo=repo,
+            title='Test issue',
+            content='We should work on this',
+            user='pingou',
+        )
+        self.session.commit()
+        self.assertEqual(msg.title, 'Test issue')
+
+        # Add a non-ascii milestone to the issue but project has no milestone
+        issue = pagure.lib.search_issues(self.session, repo, issueid=1)
+        message = pagure.lib.edit_issue(
+            self.session,
+            issue=issue,
+            milestone=stone,
+            private=False,
+            user='pingou',
+        )
+        self.assertEqual(
+            message,
+            [
+                'Issue set to the milestone: k\xe4py'
+            ]
+        )
+        self.session.commit()
+
+        # View the issue
+        output = self.app.get('/test/issue/1')
+        self.assertEqual(output.status_code, 200)
+        output_text = output.get_data(as_text=True)
+        self.assertIn(
+            '<title>Issue #1: Test issue - test - Pagure</title>',
+            output_text)
+        self.assertIn(stone, output_text)
+
+    @patch('pagure.lib.git.update_git', MagicMock(return_value=True))
+    @patch('pagure.lib.notify.send_email', MagicMock(return_value=True))
+    def test_view_issue_list_no_data(self):
+        """ Test the view_issue endpoint when the issue has a custom field
+        of type list with no data attached. """
+        repo = pagure.lib.get_authorized_project(self.session, 'test')
+
+        # Add custom fields to the project
+        msg = pagure.lib.set_custom_key_fields(
+            session=self.session,
+            project=repo,
+            fields=['test1'],
+            types=['list'],
+            data=[None],
+            notify=[None]
+        )
+        self.session.commit()
+        self.assertEqual(msg, 'List of custom fields updated')
+
+        # Create issues to play with
+        msg = pagure.lib.new_issue(
+            session=self.session,
+            repo=repo,
+            title='Big problÈm!',
+            content='We should work on this',
+            user='pingou',
+        )
+        self.session.commit()
+        self.assertEqual(msg.title, 'Big problÈm!')
+
+        # Assign a value to the custom key on that ticket
+        cfield = pagure.lib.get_custom_key(
+            session=self.session,
+            project=repo,
+            keyname='test1')
+        msg = pagure.lib.set_custom_key_value(
+            session=self.session,
+            issue=msg,
+            key=cfield,
+            value='item')
+        self.session.commit()
+        self.assertEqual(msg, 'Custom field test1 adjusted to item')
+
+        user = tests.FakeUser(username='foo')
+        with tests.user_set(self.app.application, user):
+            output = self.app.get('/test/issue/1')
+            self.assertEqual(output.status_code, 200)
+
+    @patch('pagure.lib.git.update_git', MagicMock(return_value=True))
+    @patch('pagure.lib.notify.send_email', MagicMock(return_value=True))
+    def test_update_issue(self):
+        """ Test the update_issue endpoint. """
+        output = self.app.get('/test/issue/1/update')
+        self.assertEqual(output.status_code, 302)
+
+        # Create issues to play with
+        repo = pagure.lib.get_authorized_project(self.session, 'test')
+        msg = pagure.lib.new_issue(
+            session=self.session,
+            repo=repo,
+            title='Test issue',
+            content='We should work on this',
+            user='pingou',
+        )
+        self.session.commit()
+        self.assertEqual(msg.title, 'Test issue')
+
+        user = tests.FakeUser(username='foo')
+        with tests.user_set(self.app.application, user):
+            output = self.app.get('/test/issue/1')
+            self.assertEqual(output.status_code, 200)
+            output_text = output.get_data(as_text=True)
+            self.assertIn(
+                '<title>Issue #1: Test issue - test - Pagure</title>',
+                output_text)
+            self.assertNotIn(
+                '<a class="btn btn-outline-secondary btn-sm border-0"'
+                 ' href="/test/issue/1/edit" title="Edit this issue">',
+                output_text)
+            self.assertEqual(output_text.count('title="PY C (pingou)"'), 1)
+
+            csrf_token = self.get_csrf(output=output)
+
+            data = {
+                'status': 'Closed',
+                'close_status': 'fixed'
+            }
+
+            # Invalid repo
+            output = self.app.post('/bar/issue/1/update', data=data)
+            self.assertEqual(output.status_code, 404)
+
+            # Non-existing issue
+            output = self.app.post('/test/issue/100/update', data=data)
+            self.assertEqual(output.status_code, 404)
+
+            output = self.app.post(
+                '/test/issue/1/update', data=data, follow_redirects=True)
+            self.assertEqual(output.status_code, 200)
+            output_text = output.get_data(as_text=True)
+            self.assertIn(
+                '<title>Issue #1: Test issue - test - Pagure</title>',
+                output_text)
+            self.assertNotIn(
+                '<a class="btn btn-outline-secondary btn-sm border-0"'
+                 ' href="/test/issue/1/edit" title="Edit this issue">',
+                output_text)
+            self.assertFalse(
+                '<option selected value="Fixed">Fixed</option>'
+                in output_text)
+
+            # Right status, wrong csrf
+            data['close_status'] = 'Fixed'
+            output = self.app.post(
+                '/test/issue/1/update', data=data, follow_redirects=True)
+            self.assertEqual(output.status_code, 200)
+            output_text = output.get_data(as_text=True)
+            self.assertIn(
+                '<title>Issue #1: Test issue - test - Pagure</title>',
+                output_text)
+            self.assertNotIn(
+                '<a class="btn btn-outline-secondary btn-sm border-0"'
+                 ' href="/test/issue/1/edit" title="Edit this issue">',
+                output_text)
+            self.assertFalse(
+                '<option selected value="Fixed">Fixed</option>'
+                in output_text)
+
+            # status update - blocked, open_access doesn't allow changing status
+            data['csrf_token'] = csrf_token
+            output = self.app.post(
+                '/test/issue/1/update', data=data, follow_redirects=True)
+            self.assertEqual(output.status_code, 200)
+            output_text = output.get_data(as_text=True)
+            self.assertIn(
+                '<title>Issue #1: Test issue - test - Pagure</title>',
+                output_text)
+            self.assertNotIn(
+                '<a class="btn btn-outline-secondary btn-sm border-0"'
+                 ' href="/test/issue/1/edit" title="Edit this issue">',
+                output_text)
+            self.assertNotIn(
+                'Issue close_status updated to: Fixed',
+                output_text)
+            self.assertNotIn(
+                'Issue status updated to: Closed (was: Open)',
+                output_text)
+            self.assertNotIn(
+                '<option selected value="Fixed">Fixed</option>',
+                output_text)
+
+            # Add new comment
+            data = {
+                'csrf_token': csrf_token,
+                'status': 'Closed',
+                'close_status': 'Fixed',
+                'comment': 'Woohoo a second comment!',
+            }
+            output = self.app.post(
+                '/test/issue/1/update', data=data, follow_redirects=True)
+            self.assertEqual(output.status_code, 200)
+            output_text = output.get_data(as_text=True)
+            self.assertIn(
+                '<title>Issue #1: Test issue - test - Pagure</title>',
+                output_text)
+            self.assertNotIn(
+                '<a class="btn btn-outline-secondary btn-sm border-0"'
+                 ' href="/test/issue/1/edit" title="Edit this issue">',
+                output_text)
+            self.assertIn(
+                'Comment added',
+                output_text)
+            self.assertNotIn(
+                'No changes to edit',
+                output_text)
+            self.assertIn(
+                '<p>Woohoo a second comment!</p>',
+                output_text)
+            self.assertEqual(
+                output_text.count('comment_body">'), 2)
+            self.assertNotIn(
+                '<option selected value="Fixed">Fixed</option>',
+                output_text)
+            # 1: one for the original comment
+            self.assertEqual(
+                output_text.count('title="PY C (pingou)"'),
+                1)
+
+            # Add new tag
+            data = {
+                'csrf_token': csrf_token,
+                'status': 'Closed',
+                'close_status': 'Fixed',
+                'tag': 'tag2',
+            }
+            output = self.app.post(
+                '/test/issue/1/update', data=data, follow_redirects=True)
+            self.assertEqual(output.status_code, 200)
+            output_text = output.get_data(as_text=True)
+            self.assertIn(
+                '<title>Issue #1: Test issue - test - Pagure</title>',
+                output_text)
+            self.assertNotIn(
+                '<a class="btn btn-outline-secondary btn-sm border-0"'
+                 ' href="/test/issue/1/edit" title="Edit this issue">',
+                output_text)
+            self.assertIn(
+                '<p>Woohoo a second comment!</p>',
+                output_text)
+            self.assertEqual(
+                output_text.count('comment_body">'), 2)
+            self.assertNotIn(
+                '<option selected value="Fixed">Fixed</option>',
+                output_text)
+
+            # Assign issue to an non-existent user
+            data = {
+                'csrf_token': csrf_token,
+                'status': 'Closed',
+                'close_status': 'Fixed',
+                'assignee': 'ralph',
+            }
+            output = self.app.post(
+                '/test/issue/1/update', data=data, follow_redirects=True)
+            self.assertEqual(output.status_code, 200)
+            output_text = output.get_data(as_text=True)
+            self.assertIn(
+                '<title>Issue #1: Test issue - test - Pagure</title>',
+                output_text)
+            self.assertNotIn(
+                '<a class="btn btn-outline-secondary btn-sm border-0"'
+                 ' href="/test/issue/1/edit" title="Edit this issue">',
+                output_text)
+            self.assertIn(
+                'No user &#34;ralph&#34; found',
+                output_text)
+            self.assertIn(
+                '<p>Woohoo a second comment!</p>',
+                output_text)
+            self.assertEqual(
+                output_text.count('comment_body">'), 2)
+            self.assertNotIn(
+                '<option selected value="Fixed">Fixed</option>',
+                output_text)
+
+            # Assign issue properly
+            data = {
+                'csrf_token': csrf_token,
+                'status': 'Closed',
+                'close_status': 'Fixed',
+                'assignee': 'pingou',
+            }
+            output = self.app.post(
+                '/test/issue/1/update', data=data, follow_redirects=True)
+            self.assertEqual(output.status_code, 200)
+            output_text = output.get_data(as_text=True)
+            self.assertIn(
+                '<title>Issue #1: Test issue - test - Pagure</title>',
+                output_text)
+            self.assertNotIn(
+                '<a class="btn btn-outline-secondary btn-sm border-0"'
+                 ' href="/test/issue/1/edit" title="Edit this issue">',
+                output_text)
+            self.assertIn('Issue assigned to pingou', output_text)
+            self.assertIn(
+                '<a href="/test/issues?assignee=pingou" title="PY C (pingou)"',
+                output_text)
+            self.assertIn(
+                '<p>Woohoo a second comment!</p>', output_text)
+            self.assertEqual(
+                output_text.count('comment_body">'), 2)
+            self.assertNotIn(
+                '<option selected value="Fixed">Fixed</option>',
+                output_text)
+
+        # Create another issue with a dependency
+        repo = pagure.lib.get_authorized_project(self.session, 'test')
+        msg = pagure.lib.new_issue(
+            session=self.session,
+            repo=repo,
+            title='Test issue',
+            content='We should work on this',
+            user='pingou',
+        )
+        self.session.commit()
+        self.assertEqual(msg.title, 'Test issue')
+
+        # Reset the status of the first issue
+        parent_issue = pagure.lib.search_issues(
+            self.session, repo, issueid=1)
+        parent_issue.status = 'Open'
+        self.session.add(parent_issue)
+        # Add the dependency relationship
+        self.session.add(parent_issue)
+        issue = pagure.lib.search_issues(self.session, repo, issueid=2)
+        issue.parents.append(parent_issue)
+        self.session.add(issue)
+        self.session.commit()
+
+        with tests.user_set(self.app.application, user):
+
+            data['csrf_token'] = csrf_token
+            output = self.app.post(
+                '/test/issue/2/update', data=data, follow_redirects=True)
+            self.assertEqual(output.status_code, 200)
+            output_text = output.get_data(as_text=True)
+            self.assertIn(
+                '<title>Issue #2: Test issue - test - Pagure</title>',
+                output_text)
+            self.assertNotIn(
+                '<a class="btn btn-outline-secondary btn-sm border-0"'
+                 ' href="/test/issue/2/edit" title="Edit this issue">',
+                output_text)
+            self.assertNotIn(
+                'You cannot close a ticket '
+                'that has ticket depending that are still open.',
+                output_text)
+            self.assertNotIn(
+                '<option selected value="Open">Open</option>',
+                output_text)
+
+        # Create private issue
+        repo = pagure.lib.get_authorized_project(self.session, 'test')
+        msg = pagure.lib.new_issue(
+            session=self.session,
+            repo=repo,
+            title='Test issue',
+            content='We should work on this',
+            user='pingou',
+            private=True,
+        )
+        self.session.commit()
+        self.assertEqual(msg.title, 'Test issue')
+
+        # Wrong user
+        user = tests.FakeUser()
+        with tests.user_set(self.app.application, user):
+            output = self.app.post(
+                '/test/issue/3/update', data=data, follow_redirects=True)
+            self.assertEqual(output.status_code, 403)
+
+        # Project w/o issue tracker
+        repo = pagure.lib.get_authorized_project(self.session, 'test')
+        repo.settings = {'issue_tracker': False}
+        self.session.add(repo)
+        self.session.commit()
+
+        with tests.user_set(self.app.application, user):
+            # Repo not set-up for issue tracker
+            output = self.app.post('/test/issue/1/update', data=data)
+            self.assertEqual(output.status_code, 404)
+
+    @patch('pagure.lib.git.update_git', MagicMock(return_value=True))
+    @patch('pagure.lib.notify.send_email', MagicMock(return_value=True))
+    def test_update_issue_depend(self):
+        """ Test adding dependency via the update_issue endpoint. """
+        # Create issues to play with
+        repo = pagure.lib.get_authorized_project(self.session, 'test')
+        msg = pagure.lib.new_issue(
+            session=self.session,
+            repo=repo,
+            title='Test issue',
+            content='We should work on this',
+            user='pingou',
+        )
+        self.session.commit()
+        self.assertEqual(msg.title, 'Test issue')
+
+        repo = pagure.lib.get_authorized_project(self.session, 'test')
+        msg = pagure.lib.new_issue(
+            session=self.session,
+            repo=repo,
+            title='Test issue #2',
+            content='We should work on this again',
+            user='foo',
+        )
+        self.session.commit()
+        self.assertEqual(msg.title, 'Test issue #2')
+
+        user = tests.FakeUser(username='foo')
+        with tests.user_set(self.app.application, user):
+            output = self.app.get('/test/issue/1')
+            self.assertEqual(output.status_code, 200)
+            output_text = output.get_data(as_text=True)
+            self.assertIn(
+                '<title>Issue #1: Test issue - test - Pagure</title>',
+                output_text)
+            self.assertNotIn(
+                '<a class="btn btn-outline-secondary btn-sm border-0"'
+                 ' href="/test/issue/1/edit" title="Edit this issue">',
+                output_text)
+
+            csrf_token = self.get_csrf(output=output)
+
+            # Add a dependent ticket
+            data = {
+                'csrf_token': csrf_token,
+                'depending': '2',
+            }
+            output = self.app.post(
+                '/test/issue/1/update', data=data, follow_redirects=True)
+            self.assertEqual(output.status_code, 200)
+            output_text = output.get_data(as_text=True)
+            self.assertIn(
+                '<title>Issue #1: Test issue - test - Pagure</title>',
+                output_text)
+            self.assertNotIn(
+                '<a class="btn btn-outline-secondary btn-sm border-0"'
+                 ' href="/test/issue/1/edit" title="Edit this issue">',
+                output_text)
+
+            # Add an invalid dependent ticket
+            data = {
+                'csrf_token': csrf_token,
+                'depending': '2,abc',
+            }
+            output = self.app.post(
+                '/test/issue/1/update', data=data, follow_redirects=True)
+            self.assertEqual(output.status_code, 200)
+            output_text = output.get_data(as_text=True)
+            self.assertIn(
+                '<title>Issue #1: Test issue - test - Pagure</title>',
+                output_text)
+            self.assertNotIn(
+                '<a class="btn btn-outline-secondary btn-sm border-0"'
+                 ' href="/test/issue/1/edit" title="Edit this issue">',
+                output_text)
+            self.assertNotIn(
+                'Successfully edited issue #1',
+                output_text)
+
+        repo = pagure.lib.get_authorized_project(self.session, 'test')
+        issue = pagure.lib.search_issues(self.session, repo, issueid=1)
+        self.assertEqual(issue.depending_text, [2])
+        self.assertEqual(issue.blocking_text, [])
+
+    @patch('pagure.lib.git.update_git', MagicMock(return_value=True))
+    @patch('pagure.lib.notify.send_email', MagicMock(return_value=True))
+    def test_update_issue_block(self):
+        """ Test adding blocked issue via the update_issue endpoint. """
+        # Create issues to play with
+        repo = pagure.lib.get_authorized_project(self.session, 'test')
+        msg = pagure.lib.new_issue(
+            session=self.session,
+            repo=repo,
+            title='Test issue',
+            content='We should work on this',
+            user='pingou',
+        )
+        self.session.commit()
+        self.assertEqual(msg.title, 'Test issue')
+
+        repo = pagure.lib.get_authorized_project(self.session, 'test')
+        msg = pagure.lib.new_issue(
+            session=self.session,
+            repo=repo,
+            title='Test issue #2',
+            content='We should work on this again',
+            user='foo',
+        )
+        self.session.commit()
+        self.assertEqual(msg.title, 'Test issue #2')
+
+        # User is not an admin of the project
+        user = tests.FakeUser(username='foo')
+        with tests.user_set(self.app.application, user):
+            output = self.app.get('/test/issue/1')
+            self.assertEqual(output.status_code, 200)
+            self.assertIn(
+                '<title>Issue #1: Test issue - test - Pagure</title>',
+                output.get_data(as_text=True))
+
+            csrf_token = self.get_csrf(output=output)
+
+            # Add a dependent ticket
+            data = {
+                'csrf_token': csrf_token,
+                'blocking': '2',
+            }
+            output = self.app.post(
+                '/test/issue/1/update', data=data, follow_redirects=True)
+            self.assertEqual(output.status_code, 200)
+            self.assertIn(
+                '<title>Issue #1: Test issue - test - Pagure</title>',
+                output.get_data(as_text=True))
+
+            repo = pagure.lib.get_authorized_project(self.session, 'test')
+            issue = pagure.lib.search_issues(self.session, repo, issueid=1)
+            self.assertEqual(issue.depending_text, [])
+            self.assertEqual(issue.blocking_text, [2])
+
+            # Add an invalid dependent ticket
+            data = {
+                'csrf_token': csrf_token,
+                'blocking': '2,abc',
+            }
+            output = self.app.post(
+                '/test/issue/1/update', data=data, follow_redirects=True)
+            self.assertEqual(output.status_code, 200)
+            output_text = output.get_data(as_text=True)
+            self.assertIn(
+                '<title>Issue #1: Test issue - test - Pagure</title>',
+                output_text)
+            self.assertNotIn(
+                '<a class="btn btn-outline-secondary btn-sm border-0"'
+                 ' href="/test/issue/1/edit" title="Edit this issue">',
+                output_text)
+            self.assertNotIn(
+                'Successfully edited issue #1',
+                output_text)
+
+        self.session.commit()
+        repo = pagure.lib.get_authorized_project(self.session, 'test')
+        issue = pagure.lib.search_issues(self.session, repo, issueid=1)
+        self.assertEqual(issue.depending_text, [])
+        self.assertEqual(issue.blocking_text, [2])
+
+    @patch('pagure.lib.git.update_git', MagicMock(return_value=True))
+    @patch('pagure.lib.notify.send_email', MagicMock(return_value=True))
+    def test_update_issue_edit_comment(self):
+        """ Test the issues edit comment endpoint """
+        # Create issues to play with
+        repo = pagure.lib.get_authorized_project(self.session, 'test')
+        msg = pagure.lib.new_issue(
+            session=self.session,
+            repo=repo,
+            title='Test issue',
+            content='We should work on this',
+            user='pingou',
+        )
+        self.session.commit()
+        self.assertEqual(msg.title, 'Test issue')
+
+        user = tests.FakeUser(username='foo')
+        with tests.user_set(self.app.application, user):
+            output = self.app.get('/test/issue/1')
+            self.assertEqual(output.status_code, 200)
+            output_text = output.get_data(as_text=True)
+            self.assertIn(
+                '<title>Issue #1: Test issue - test - Pagure</title>',
+                output_text)
+            self.assertNotIn(
+                '<a class="btn btn-outline-secondary btn-sm border-0"'
+                ' href="/test/issue/1/edit" title="Edit this issue">\n',
+                output_text)
+
+            csrf_token = self.get_csrf(output=output)
+
+            # Add new comment
+            data = {
+                'csrf_token': csrf_token,
+                'comment': 'Woohoo a second comment!',
+            }
+            output = self.app.post(
+                '/test/issue/1/update', data=data, follow_redirects=True)
+            self.assertEqual(output.status_code, 200)
+            output_text = output.get_data(as_text=True)
+            self.assertIn(
+                '<title>Issue #1: Test issue - test - Pagure</title>',
+                output_text)
+            self.assertNotIn(
+                '<a class="btn btn-outline-secondary btn-sm border-0"'
+                ' href="/test/issue/1/edit" title="Edit this issue">\n',
+                output_text)
+            self.assertIn('Comment added', output_text)
+            self.assertIn(
+                '<p>Woohoo a second comment!</p>',
+                output_text)
+            self.assertEqual(
+                output_text.count('comment_body">'), 2)
+
+        repo = pagure.lib.get_authorized_project(self.session, 'test')
+        issue = pagure.lib.search_issues(self.session, repo, issueid=1)
+        self.assertEqual(len(issue.comments), 1)
+        self.assertEqual(
+            issue.comments[0].comment,
+            'Woohoo a second comment!')
+
+        data = {
+            'csrf_token': csrf_token,
+            'edit_comment': 1,
+            'update_comment': 'Updated comment',
+        }
+
+        user = tests.FakeUser()
+        with tests.user_set(self.app.application, user):
+            # Wrong issue id
+            output = self.app.post(
+                '/test/issue/3/update', data=data, follow_redirects=True)
+            self.assertEqual(output.status_code, 404)
+
+            # Wrong user
+            output = self.app.post(
+                '/test/issue/1/update', data=data, follow_redirects=True)
+            self.assertEqual(output.status_code, 403)
+
+        user = tests.FakeUser(username='foo')
+        with tests.user_set(self.app.application, user):
+            # Edit comment
+            output = self.app.post(
+                '/test/issue/1/update', data=data, follow_redirects=True)
+            self.assertEqual(output.status_code, 200)
+            output_text = output.get_data(as_text=True)
+            self.assertIn(
+                '<title>Issue #1: Test issue - test - Pagure</title>',
+                output_text)
+            self.assertNotIn(
+                '<a class="btn btn-outline-secondary btn-sm border-0"'
+                 ' href="/test/issue/1/edit" title="Edit this issue">',
+                output_text)
+            self.assertIn('Comment updated', output_text)
+
+        self.session.commit()
+        repo = pagure.lib.get_authorized_project(self.session, 'test')
+        issue = pagure.lib.search_issues(self.session, repo, issueid=1)
+        self.assertEqual(len(issue.comments), 1)
+        self.assertEqual(issue.comments[0].comment, 'Updated comment')
+
+        with tests.user_set(self.app.application, user):
+            output = self.app.get('/test/issue/1/comment/1/edit')
+            output_text = output.get_data(as_text=True)
+            self.assertIn(
+                '<title>test - Pagure</title>', output_text)
+            self.assertTrue('<div id="edit">' in output_text)
+            self.assertTrue('<section class="edit_comment">' in output_text)
+            self.assertTrue(
+                '<textarea class="form-control" id="update_comment"'
+                in output_text)
+
+            csrf_token = self.get_csrf(output=output)
+
+            data['csrf_token'] = csrf_token
+            data['update_comment'] = 'Second update'
+
+            # Edit the comment with the other endpoint
+            output = self.app.post(
+                '/test/issue/1/comment/1/edit',
+                data=data,
+                follow_redirects=True)
+            self.assertEqual(output.status_code, 200)
+            output_text = output.get_data(as_text=True)
+            self.assertIn(
+                '<title>Issue #1: Test issue - test - Pagure</title>',
+                output_text)
+            self.assertNotIn(
+                '<a class="btn btn-outline-secondary btn-sm border-0"'
+                 ' href="/test/issue/1/edit" title="Edit this issue">',
+                output_text)
+            self.assertIn('Comment updated', output_text)
+
+        self.session.commit()
+        repo = pagure.lib.get_authorized_project(self.session, 'test')
+        issue = pagure.lib.search_issues(self.session, repo, issueid=1)
+        self.assertEqual(len(issue.comments), 1)
+        self.assertEqual(issue.comments[0].comment, 'Second update')
+
+        # Create another issue from someone else
+        repo = pagure.lib.get_authorized_project(self.session, 'test')
+        msg = pagure.lib.new_issue(
+            session=self.session,
+            repo=repo,
+            title='Test issue',
+            content='We should work on this',
+            user='foo',
+        )
+        self.session.commit()
+        self.assertEqual(msg.title, 'Test issue')
+
+        issue = pagure.lib.search_issues(self.session, repo, issueid=1)
+        self.assertEqual(len(issue.comments), 1)
+        self.assertEqual(issue.status, 'Open')
+
+        issue = pagure.lib.search_issues(self.session, repo, issueid=2)
+        self.assertEqual(len(issue.comments), 0)
+        self.assertEqual(issue.status, 'Open')
+
+        user = tests.FakeUser(username="foo")
+        with tests.user_set(self.app.application, user):
+            data = {
+                'csrf_token': csrf_token,
+                'comment': 'Nevermind figured it out',
+                'status': 'Closed',
+                'close_status': 'Invalid'
+            }
+
+            # Add a comment and close the ticket #1
+            output = self.app.post(
+                '/test/issue/1/update', data=data, follow_redirects=True)
+            self.assertEqual(output.status_code, 200)
+            output_text = output.get_data(as_text=True)
+            self.assertNotIn('Successfully edited issue #1\n', output_text)
+            self.assertIn('Comment added', output_text)
+            self.assertIn(
+                '<a class="btn btn-outline-primary border-0 btn-sm issue-metadata-display'
+                ' editmetadatatoggle" href="javascript:void(0)" style="display: inline-block;">'
+                '<i class="fa fa-fw fa-pencil">',
+                output_text
+            )
+
+            data = {
+                'csrf_token': csrf_token,
+                'comment': 'Nevermind figured it out',
+                'status': 'Closed',
+                'close_status': 'Invalid'
+            }
+
+            # Add a comment and close the ticket #2
+            output = self.app.post(
+                '/test/issue/2/update', data=data, follow_redirects=True)
+            self.assertEqual(output.status_code, 200)
+            output_text = output.get_data(as_text=True)
+            self.assertIn(
+                'Issue close_status updated to: Invalid',
+                output_text
+            )
+            self.assertIn('Comment added', output_text)
+            self.assertIn(
+                'Issue status updated to: Closed (was: Open)',
+                output_text
+            )
+            self.assertIn(
+                '<a class="btn btn-outline-primary border-0 btn-sm issue-metadata-display'
+                ' editmetadatatoggle" href="javascript:void(0)" style="display: inline-block;">'
+                '<i class="fa fa-fw fa-pencil">',
+                output_text
+            )
+
+        # Ticket #1 has one more comment and is still open
+        self.session.commit()
+        issue = pagure.lib.search_issues(self.session, repo, issueid=1)
+        self.assertEqual(len(issue.comments), 2)
+        self.assertEqual(issue.status, 'Open')
+
+        # Ticket #2 has one less comment and is closed
+        issue = pagure.lib.search_issues(self.session, repo, issueid=2)
+        self.assertEqual(len(issue.comments), 2)
+        self.assertEqual(
+            issue.comments[0].comment,
+            'Nevermind figured it out')
+        self.assertEqual(
+            issue.comments[1].comment,
+            '**Metadata Update from @foo**:\n'
+            '- Issue close_status updated to: Invalid\n'
+            '- Issue status updated to: Closed (was: Open)')
+        self.assertEqual(issue.status, 'Closed')
+
+    @patch('pagure.lib.git.update_git', MagicMock(return_value=True))
+    @patch('pagure.lib.notify.send_email', MagicMock(return_value=True))
+    def test_view_issue_closed(self):
+        """ Test viewing a closed issue. """
+        # Create issues to play with
+        repo = pagure.lib.get_authorized_project(self.session, 'test')
+        msg = pagure.lib.new_issue(
+            session=self.session,
+            repo=repo,
+            title='Test issue',
+            content='We should work on this',
+            user='pingou',
+        )
+        self.session.commit()
+        self.assertEqual(msg.title, 'Test issue')
+
+        user = tests.FakeUser(username='foo')
+        with tests.user_set(self.app.application, user):
+            output = self.app.get('/test/issue/1')
+            self.assertEqual(output.status_code, 200)
+            output_text = output.get_data(as_text=True)
+            self.assertIn(
+                '<title>Issue #1: Test issue - test - Pagure</title>',
+                output_text)
+            self.assertNotIn(
+                '<a class="btn btn-outline-secondary btn-sm border-0"'
+                 ' href="/test/issue/1/edit" title="Edit this issue">',
+                output_text)
+
+            csrf_token = self.get_csrf(output=output)
+
+            # Add new comment
+            data = {
+                'csrf_token': csrf_token,
+                'status': 'Closed',
+                'close_status': 'Fixed',
+                'comment': 'Woohoo a second comment!',
+            }
+            output = self.app.post(
+                '/test/issue/1/update', data=data, follow_redirects=True)
+            self.assertEqual(output.status_code, 200)
+            output_text = output.get_data(as_text=True)
+            self.assertIn(
+                '<title>Issue #1: Test issue - test - Pagure</title>',
+                output_text)
+            self.assertNotIn(
+                '<a class="btn btn-outline-secondary btn-sm border-0"'
+                 ' href="/test/issue/1/edit" title="Edit this issue">',
+                output_text)
+            self.assertIn('Comment added', output_text)
+            self.assertIn(
+                '<p>Woohoo a second comment!</p>', output_text)
+            self.assertEqual(output_text.count('comment_body">'), 2)
+            self.assertNotIn(
+                '<option selected value="Fixed">Fixed</option>',
+                output_text)
+
+
+if __name__ == '__main__':
+    unittest.main(verbosity=2)
diff --git a/tests/test_pagure_flask_ui_issues_private.py b/tests/test_pagure_flask_ui_issues_private.py
index f9a7291..9ed4a5b 100644
--- a/tests/test_pagure_flask_ui_issues_private.py
+++ b/tests/test_pagure_flask_ui_issues_private.py
@@ -221,7 +221,7 @@ class PagureFlaskIssuesPrivatetests(tests.Modeltests):
                 output_text)
             self.assertIn(
                 '<span class="fa fa-fw text-success fa-exclamation-circle pt-1"></span>\n'
-                '              <span class="text-success font-weight-bold">#1</span>\n',
+                '            <span class="text-success font-weight-bold">#1</span>\n',
                 output_text)
 
     def test_view_issue_author(self):
@@ -238,7 +238,7 @@ class PagureFlaskIssuesPrivatetests(tests.Modeltests):
                 output_text)
             self.assertIn(
                 '<span class="fa fa-fw text-success fa-exclamation-circle pt-1"></span>\n'
-                '              <span class="text-success font-weight-bold">#1</span>\n',
+                '            <span class="text-success font-weight-bold">#1</span>\n',
                 output_text)
 
     def test_view_issue_authenticated(self):
@@ -296,7 +296,7 @@ class PagureFlaskIssuesPrivatetests(tests.Modeltests):
                 output_text)
             self.assertIn(
                 '<span class="fa fa-fw text-success fa-exclamation-circle pt-1"></span>\n'
-                '              <span class="text-success font-weight-bold">#1</span>\n',
+                '            <span class="text-success font-weight-bold">#1</span>\n',
                 output_text)
 
     def test_view_issue_authenticated_assigned(self):
@@ -320,7 +320,7 @@ class PagureFlaskIssuesPrivatetests(tests.Modeltests):
                 output_text)
             self.assertIn(
                 '<span class="fa fa-fw text-success fa-exclamation-circle pt-1"></span>\n'
-                '              <span class="text-success font-weight-bold">#1</span>\n',
+                '            <span class="text-success font-weight-bold">#1</span>\n',
                 output_text)
 
 
diff --git a/tests/test_pagure_lib_git.py b/tests/test_pagure_lib_git.py
index cf481ee..d9f10b2 100644
--- a/tests/test_pagure_lib_git.py
+++ b/tests/test_pagure_lib_git.py
@@ -1698,7 +1698,7 @@ new file mode 100644
 index 0000000..60f7480
 --- /dev/null
 +++ b/456
-@@ -0,0 +1,141 @@
+@@ -0,0 +1,143 @@
 +{
 +    "assignee": null,
 +    "branch": "master",
@@ -1752,6 +1752,7 @@ index 0000000..60f7480
 +            "issues_default_to_private": false,
 +            "notify_on_commit_flag": false,
 +            "notify_on_pull-request_flag": false,
++            "open_metadata_access_to_all": false,
 +            "project_documentation": false,
 +            "pull_request_access_only": false,
 +            "pull_requests": true,
@@ -1809,6 +1810,7 @@ index 0000000..60f7480
 +            "issues_default_to_private": false,
 +            "notify_on_commit_flag": false,
 +            "notify_on_pull-request_flag": false,
++            "open_metadata_access_to_all": false,
 +            "project_documentation": false,
 +            "pull_request_access_only": false,
 +            "pull_requests": true,

From 11cf8810be31eb8e5176e4d09f4466b1287bcb26 Mon Sep 17 00:00:00 2001
From: Pierre-Yves Chibon <pingou@pingoured.fr>
Date: Oct 05 2018 18:48:34 +0000
Subject: [PATCH 2/2] Drop un-used imports


Signed-off-by: Pierre-Yves Chibon <pingou@pingoured.fr>

---

diff --git a/pagure/hooks/pagure_force_commit.py b/pagure/hooks/pagure_force_commit.py
index 7f18ccc..b46f9c7 100644
--- a/pagure/hooks/pagure_force_commit.py
+++ b/pagure/hooks/pagure_force_commit.py
@@ -10,8 +10,6 @@
 
 from __future__ import unicode_literals
 
-import sys
-
 import sqlalchemy as sa
 import wtforms
 
diff --git a/pagure/hooks/pagure_no_new_branches.py b/pagure/hooks/pagure_no_new_branches.py
index fe924a0..69d51cf 100644
--- a/pagure/hooks/pagure_no_new_branches.py
+++ b/pagure/hooks/pagure_no_new_branches.py
@@ -9,7 +9,6 @@
 """
 
 from __future__ import unicode_literals
-import sys
 
 import sqlalchemy as sa
 import wtforms
diff --git a/pagure/hooks/pagure_unsigned_commits.py b/pagure/hooks/pagure_unsigned_commits.py
index 00afeee..f60017e 100644
--- a/pagure/hooks/pagure_unsigned_commits.py
+++ b/pagure/hooks/pagure_unsigned_commits.py
@@ -10,8 +10,6 @@
 
 from __future__ import unicode_literals
 
-import sys
-
 import sqlalchemy as sa
 import wtforms
 

