From 00f2c8d0e06401f435b0d0038fd02da71ae216f4 Mon Sep 17 00:00:00 2001 From: Gaurav Kumar Date: Jun 14 2016 13:11:20 +0000 Subject: [PATCH 1/10] Added watch feature. --- diff --git a/pagure/forms.py b/pagure/forms.py index a9d1e0a..3072838 100644 --- a/pagure/forms.py +++ b/pagure/forms.py @@ -20,6 +20,21 @@ PROJECT_NAME_REGEX = \ '^[a-zA-z0-9_][a-zA-Z0-9-_]*(/?[a-zA-z0-9_][a-zA-Z0-9-_]+)?$' +class WatchForm(wtf.Form): + ''' Form to update the watch status of a project for the requested user. ''' + repo_name = wtforms.TextField( + 'Repo name', + [wtforms.validators.Required()] + ) + repo_user = wtforms.TextField( + 'Repo User', + [wtforms.validators.optional()] + ) + watch = wtforms.TextField( + 'Watch', + [wtforms.validators.Required()] + ) + class ProjectFormSimplified(wtf.Form): ''' Form to edit the description of a project. ''' description = wtforms.TextField( diff --git a/pagure/lib/__init__.py b/pagure/lib/__init__.py index 8f36cd7..c00b940 100644 --- a/pagure/lib/__init__.py +++ b/pagure/lib/__init__.py @@ -2748,3 +2748,38 @@ def get_pull_request_of_user(session, username): ) return query.all() + +def update_watch_status(session, project, user, watch): + ''' Update the user status for watching a project. + ''' + user_obj = __get_user(session, user) + + if not user_obj: + raise pagure.exceptions.PagureException( + 'No user with username: %s' % user) + + watcher = session.query( + model.Watcher + ).filter( + sqlalchemy.and_( + model.Watcher.project_id == project.id, + model.Watcher.user_id == user_obj.id, + ) + ).first() + + if not watcher: + watcher = model.Watcher( + project_id=project.id, + user_id=user_obj.id, + watch=watch + ) + else: + watcher.watch = watch + + session.add(watcher) + session.flush() + + msg_success = 'From now you are watching this repo.' + if not int(watch): + msg_success = 'You are no longer watching this repo.' + return msg_success diff --git a/pagure/lib/model.py b/pagure/lib/model.py index 4ba212f..e4ec0a2 100644 --- a/pagure/lib/model.py +++ b/pagure/lib/model.py @@ -332,6 +332,10 @@ class Project(BASE): backref="projects", ) + unwatchers = relation("Watcher", + primaryjoin="and_(Project.id==Watcher.project_id, " + "Watcher.watch=='1')") + @property def path(self): ''' Return the name of the git repo on the filesystem. ''' @@ -482,6 +486,31 @@ class Project(BASE): return output + @property + def watchers_list(self): + ''' Return the list of username of users watching this project. + ''' + + watchers = set([self.user.user]) + for user in self.users: + if user.default_email: + watchers.add(user.user) + + for group in self.groups: + if group.creator.default_email: + watchers.add(group.creator.user) + for user in group.users: + if user.default_email: + watchers.add(user.default_email) + + for watcher in self.watchers: + if watcher.user.default_email and not watcher.watch: + watchers.delete(watcher.user.user) + if watcher.user.user not in watchers: + watchers.add(watcher.user.user) + + return watchers + class ProjectUser(BASE): """ Stores the user of a projects. @@ -1296,6 +1325,45 @@ class ProjectGroup(BASE): 'project_id', 'group_id'), ) +class Watcher(BASE): + """ Stores the user of a projects. + + Table -- watchers + """ + + __tablename__ = 'watchers' + __table_args__ = ( + sa.UniqueConstraint('project_id', 'user_id'), + ) + + id = sa.Column(sa.Integer, primary_key=True) + project_id = sa.Column( + sa.Integer, + sa.ForeignKey('projects.id', onupdate='CASCADE'), + nullable=False) + user_id = sa.Column( + sa.Integer, + sa.ForeignKey('users.id', onupdate='CASCADE'), + nullable=False, + index=True) + watch = sa.Column( + sa.Boolean, + nullable=False) + + user = relation( + 'User', foreign_keys=[user_id], remote_side=[User.id], + backref=backref( + 'watchers', cascade="delete, delete-orphan" + ), + ) + + project = relation( + 'Project', foreign_keys=[project_id], remote_side=[Project.id], + backref=backref( + 'watchers', cascade="delete, delete-orphan", + ), + ) + # # Class and tables specific for the API/token access # diff --git a/pagure/lib/notify.py b/pagure/lib/notify.py index 9e501d8..af58dc2 100644 --- a/pagure/lib/notify.py +++ b/pagure/lib/notify.py @@ -122,6 +122,11 @@ def _get_emails_for_issue(issue): if issue.assignee and issue.assignee.default_email: emails.add(issue.assignee.default_email) + # Remove the person list in unwatch + for unwatcher in issue.project.unwatchers: + if unwatcher.user.default_email in emails: + emails.delete(unwatcher.user.default_email) + # Drop the email used by pagure when sending emails = _clean_emails( emails, pagure.APP.config.get(pagure.APP.config.get( diff --git a/pagure/static/pagure.css b/pagure/static/pagure.css index ba55e94..7af4cd3 100644 --- a/pagure/static/pagure.css +++ b/pagure/static/pagure.css @@ -538,6 +538,7 @@ th[data-sort] { cursor: pointer; } +<<<<<<< 8e8c5a2fffb08f092e660188ae889367a5523ddd .pr-changes-description { font-size: 0.8em; @@ -560,4 +561,8 @@ th[data-sort] { } @media (min-width:1200px) { .pr_comment_form .card, .inline-pr-comment .card{max-width:680px} +======= +#watch_project a { + cursor: pointer; +>>>>>>> Added watch feature. } diff --git a/pagure/templates/repo_master.html b/pagure/templates/repo_master.html index b33fa8f..f319938 100644 --- a/pagure/templates/repo_master.html +++ b/pagure/templates/repo_master.html @@ -27,6 +27,61 @@ id="fork_project"> Fork {{ forkbuttonform.csrf_token }} +<<<<<<< 8e8c5a2fffb08f092e660188ae889367a5523ddd +======= + {% endif %} + + {% if authenticated %} +
+ + + +
+ {% endif %} + {% if branchname %} +
+ + + +>>>>>>> Added watch feature.
{% endif %} @@ -185,3 +240,25 @@ {% endblock %} {% endblock %} + +{% block jscripts %} +{{ super() }} + +{% endblock %} \ No newline at end of file diff --git a/pagure/ui/repo.py b/pagure/ui/repo.py index 79aa0d4..5f28240 100644 --- a/pagure/ui/repo.py +++ b/pagure/ui/repo.py @@ -1831,3 +1831,36 @@ def view_project_activity(repo): 'activity.html', repo=repo_obj, ) + +@APP.route('/watch', methods=['POST']) +@APP.route('/watch/', methods=['POST']) +def watch_repo(): + """ Marked for watching or Unwatching + """ + previous_url = flask.request.referrer + form = pagure.forms.WatchForm() + if form.validate_on_submit(): + watch = form.watch.data + repo_name = form.repo_name.data + repo_user = form.repo_user.data + username = flask.g.fas_user.username + if repo_user: + repo_obj = pagure.lib.get_project(SESSION, repo_name, repo_user) + else: + repo_obj = pagure.lib.get_project(SESSION, repo_name) + + if not repo_obj: + flask.abort(404, 'Project not found') + + try: + msg = pagure.lib.update_watch_status( + SESSION, repo_obj, + username, watch + ) + SESSION.commit() + flask.flash(msg) + except pagure.exceptions.PagureException as msg: + SESSION.rollback() + flask.flash(msg, 'error') + + return flask.redirect(previous_url) diff --git a/tests/test_pagure_flask_ui_repo.py b/tests/test_pagure_flask_ui_repo.py index 62f0462..9c2fcbd 100644 --- a/tests/test_pagure_flask_ui_repo.py +++ b/tests/test_pagure_flask_ui_repo.py @@ -2922,6 +2922,57 @@ index 0000000..fb7093d output = self.app.get('/foo/activity/') self.assertEqual(output.status_code, 404) + def test_watch_repo(self): + """ Test the watch_repo endpoint. """ + + output = self.app.get('/foo/watch') + self.assertEqual(output.status_code, 404) + tests.create_projects(self.session) + + user = tests.FakeUser() + user.username = 'pingou' + with tests.user_set(pagure.APP, user): + + output = self.app.get('/new/') + self.assertEqual(output.status_code, 200) + self.assertIn('Create new Project', output.data) + + csrf_token = output.data.split( + 'name="csrf_token" type="hidden" value="')[1].split('">')[0] + + data = { + 'csrf_token': csrf_token + } + data['repo_name'] = 'test' + data['watch'] = 1 + + output = self.app.post( + '/watch/', data=data) + self.assertEqual(output.status_code, 302) + repo = pagure.lib.get_project(self.session, 'test') + msg = pagure.lib.update_watch_status( + session=self.session, + project=repo, + user=user.username, + watch=data['watch'], + ) + self.session.commit() + self.assertEqual(msg, 'From now you are watching this repo.') + + data['watch'] = 0 + + output = self.app.post( + '/watch/', data=data) + self.assertEqual(output.status_code, 302) + repo = pagure.lib.get_project(self.session, 'test') + msg = pagure.lib.update_watch_status( + session=self.session, + project=repo, + user=user.username, + watch=data['watch'], + ) + self.session.commit() + self.assertEqual(msg, 'You are no longer watching this repo.') if __name__ == '__main__': SUITE = unittest.TestLoader().loadTestsFromTestCase(PagureFlaskRepotests) From 604af68560d61c2bc7b78f9ae8d02348fafeccf5 Mon Sep 17 00:00:00 2001 From: Gaurav Kumar Date: Jun 14 2016 13:11:20 +0000 Subject: [PATCH 2/10] Few modifications for watch feature. --- diff --git a/pagure/forms.py b/pagure/forms.py index 3072838..a9d1e0a 100644 --- a/pagure/forms.py +++ b/pagure/forms.py @@ -20,21 +20,6 @@ PROJECT_NAME_REGEX = \ '^[a-zA-z0-9_][a-zA-Z0-9-_]*(/?[a-zA-z0-9_][a-zA-Z0-9-_]+)?$' -class WatchForm(wtf.Form): - ''' Form to update the watch status of a project for the requested user. ''' - repo_name = wtforms.TextField( - 'Repo name', - [wtforms.validators.Required()] - ) - repo_user = wtforms.TextField( - 'Repo User', - [wtforms.validators.optional()] - ) - watch = wtforms.TextField( - 'Watch', - [wtforms.validators.Required()] - ) - class ProjectFormSimplified(wtf.Form): ''' Form to edit the description of a project. ''' description = wtforms.TextField( diff --git a/pagure/lib/__init__.py b/pagure/lib/__init__.py index c00b940..e3f33a3 100644 --- a/pagure/lib/__init__.py +++ b/pagure/lib/__init__.py @@ -2783,3 +2783,35 @@ def update_watch_status(session, project, user, watch): if not int(watch): msg_success = 'You are no longer watching this repo.' return msg_success + +def is_watching(session, user, project): + ''' Check user watching the project. ''' + + user_obj = __get_user(session, user) + if not user_obj: + raise pagure.exceptions.PagureException( + 'No user with username: %s' % user) + + watcher = session.query( + model.Watcher + ).filter( + sqlalchemy.and_( + model.Watcher.project_id == project.id, + model.Watcher.user_id == user_obj.id, + ) + ).first() + + if watcher: + return watcher.watch + + if user == project.user.user: + return True + + watch=False + for group in project.groups: + for guser in group.users: + if user == guser.username: + watch=True + break + + return watch diff --git a/pagure/static/pagure.css b/pagure/static/pagure.css index 7af4cd3..5c430ad 100644 --- a/pagure/static/pagure.css +++ b/pagure/static/pagure.css @@ -538,7 +538,6 @@ th[data-sort] { cursor: pointer; } -<<<<<<< 8e8c5a2fffb08f092e660188ae889367a5523ddd .pr-changes-description { font-size: 0.8em; @@ -561,8 +560,7 @@ th[data-sort] { } @media (min-width:1200px) { .pr_comment_form .card, .inline-pr-comment .card{max-width:680px} -======= + #watch_project a { cursor: pointer; ->>>>>>> Added watch feature. } diff --git a/pagure/templates/repo_master.html b/pagure/templates/repo_master.html index f319938..a49cf4c 100644 --- a/pagure/templates/repo_master.html +++ b/pagure/templates/repo_master.html @@ -27,8 +27,6 @@ id="fork_project"> Fork {{ forkbuttonform.csrf_token }} -<<<<<<< 8e8c5a2fffb08f092e660188ae889367a5523ddd -======= {% endif %} {% if authenticated %} @@ -38,13 +36,10 @@ Toggle Dropdown @@ -81,7 +76,6 @@ {% endif %} {% endfor %} ->>>>>>> Added watch feature. {% endif %} @@ -248,12 +242,14 @@ $(document).ready(function() { $(".watch-menu a").click(function(){ var selectedValue = $(this).text(); var currentValue = $("#watch-button").text(); + var action = $("#watch_project").attr('action'); if (currentValue == selectedValue) { if (selectedValue != "Watch") { - $("#watch").val('0'); $("#watch-button").html("Watch"); } else { $("#watch").val('1'); + action = action.replace('/0/', '/1/'); + $('#watch_project').attr('action', action); $("#watch-button").html("Unwatch"); } $('#watch_project').submit(); diff --git a/pagure/ui/repo.py b/pagure/ui/repo.py index 5f28240..7e428d5 100644 --- a/pagure/ui/repo.py +++ b/pagure/ui/repo.py @@ -1832,35 +1832,38 @@ def view_project_activity(repo): repo=repo_obj, ) -@APP.route('/watch', methods=['POST']) -@APP.route('/watch/', methods=['POST']) -def watch_repo(): +@APP.route('/watch///', methods=['POST']) +@APP.route('/watch///', methods=['POST']) +def watch_repo(repo, watch, user=None): """ Marked for watching or Unwatching """ - previous_url = flask.request.referrer - form = pagure.forms.WatchForm() - if form.validate_on_submit(): - watch = form.watch.data - repo_name = form.repo_name.data - repo_user = form.repo_user.data - username = flask.g.fas_user.username - if repo_user: - repo_obj = pagure.lib.get_project(SESSION, repo_name, repo_user) - else: - repo_obj = pagure.lib.get_project(SESSION, repo_name) + return_point = flask.url_for('index') + if pagure.is_safe_url(flask.request.referrer): + return_point = flask.request.referrer - if not repo_obj: - flask.abort(404, 'Project not found') + form = pagure.forms.ConfirmationForm() + if not form.validate_on_submit(): + flask.abort(400) - try: - msg = pagure.lib.update_watch_status( - SESSION, repo_obj, - username, watch - ) - SESSION.commit() - flask.flash(msg) - except pagure.exceptions.PagureException as msg: - SESSION.rollback() - flask.flash(msg, 'error') + if str(watch) not in ['0', '1']: + flask.abort(400) + + username = flask.g.fas_user.username + repo_obj = pagure.lib.get_project(SESSION, repo) + if user is not None: + repo_obj = pagure.lib.get_project(SESSION, repo, user) + + if not repo_obj: + flask.abort(404, 'Project not found') + + try: + msg = pagure.lib.update_watch_status( + SESSION, repo_obj, + username, watch + ) + SESSION.commit() + flask.flash(msg) + except pagure.exceptions.PagureException as msg: + flask.flash(msg, 'error') - return flask.redirect(previous_url) + return flask.redirect(return_point) diff --git a/tests/test_pagure_flask_ui_repo.py b/tests/test_pagure_flask_ui_repo.py index 9c2fcbd..e0a2085 100644 --- a/tests/test_pagure_flask_ui_repo.py +++ b/tests/test_pagure_flask_ui_repo.py @@ -2925,8 +2925,9 @@ index 0000000..fb7093d def test_watch_repo(self): """ Test the watch_repo endpoint. """ - output = self.app.get('/foo/watch') - self.assertEqual(output.status_code, 404) + output = self.app.post('/watch/') + self.assertEqual(output.status_code, 405) + tests.create_projects(self.session) user = tests.FakeUser() @@ -2941,38 +2942,53 @@ index 0000000..fb7093d 'name="csrf_token" type="hidden" value="')[1].split('">')[0] data = { - 'csrf_token': csrf_token + 'csrf_token':csrf_token } - data['repo_name'] = 'test' - data['watch'] = 1 + output = self.app.post( + '/watch', data=data, follow_redirects=True) + self.assertEqual(output.status_code, 404) output = self.app.post( - '/watch/', data=data) - self.assertEqual(output.status_code, 302) - repo = pagure.lib.get_project(self.session, 'test') - msg = pagure.lib.update_watch_status( - session=self.session, - project=repo, - user=user.username, - watch=data['watch'], - ) - self.session.commit() - self.assertEqual(msg, 'From now you are watching this repo.') + '/watch/foo', data=data, follow_redirects=True) + self.assertEqual(output.status_code, 404) - data['watch'] = 0 + output = self.app.post( + '/watch/test/2/', data=data, follow_redirects=True) + self.assertEqual(output.status_code, 400) output = self.app.post( - '/watch/', data=data) - self.assertEqual(output.status_code, 302) - repo = pagure.lib.get_project(self.session, 'test') - msg = pagure.lib.update_watch_status( - session=self.session, - project=repo, - user=user.username, - watch=data['watch'], + '/watch/test/0/', data=data, follow_redirects=True) + self.assertIn( + '\n You are no longer' + ' watching this repo.', output.data) + + output = self.app.post( + '/watch/test/1/', data=data, follow_redirects=True) + self.assertIn( + '\n From now you are' + ' watching this repo.', output.data) + + item = pagure.lib.model.Project( + user_id=2, # foo + name='test', + description='test project #1', + hook_token='aaabbb', + parent_id=1, ) + self.session.add(item) self.session.commit() - self.assertEqual(msg, 'You are no longer watching this repo.') + + output = self.app.post( + '/watch/test/0/foo', data=data, follow_redirects=True) + self.assertIn( + '\n You are no longer' + ' watching this repo.', output.data) + + output = self.app.post( + '/watch/test/1/foo', data=data, follow_redirects=True) + self.assertIn( + '\n From now you are' + ' watching this repo.', output.data) if __name__ == '__main__': SUITE = unittest.TestLoader().loadTestsFromTestCase(PagureFlaskRepotests) From 12485a70b908aa342159073d82d5d37b95e33de4 Mon Sep 17 00:00:00 2001 From: Gaurav Kumar Date: Jun 14 2016 13:11:20 +0000 Subject: [PATCH 3/10] Set select for docs link. --- diff --git a/pagure/ui/repo.py b/pagure/ui/repo.py index 7e428d5..9faad99 100644 --- a/pagure/ui/repo.py +++ b/pagure/ui/repo.py @@ -1806,6 +1806,7 @@ def view_docs(repo, username=None, filename=None): return flask.render_template( 'docs.html', + select='docs', repo=repo_obj, username=username, filename=filename, From f3f4347f18d8a53c25c6489a65f40076b01b87fb Mon Sep 17 00:00:00 2001 From: Gaurav Kumar Date: Jun 14 2016 13:11:20 +0000 Subject: [PATCH 4/10] Modifications in watch_repo route. --- diff --git a/pagure/templates/repo_master.html b/pagure/templates/repo_master.html index a49cf4c..e47258d 100644 --- a/pagure/templates/repo_master.html +++ b/pagure/templates/repo_master.html @@ -36,7 +36,7 @@ Toggle Dropdown