From 1463854a45f0aef4a9b4ddb0e7415b7edcbc5bd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89lie=20Bouttier?= Date: Sat, 30 Aug 2014 15:38:06 -0700 Subject: [PATCH] first commit for v0.2 --- {issue/migrations => accounts}/__init__.py | 0 accounts/admin.py | 7 + accounts/forms.py | 17 + accounts/migrations/0001_initial.py | 68 + .../migrations}/__init__.py | 0 accounts/models.py | 72 + accounts/static/css/jquery-ui.css | 1225 ++ accounts/static/js/accounts.js | 29 + .../static/js/delete_modal.js | 2 + accounts/static/js/team.js | 13 + accounts/templatetags/__init__.py | 0 accounts/templatetags/accounts_tags.py | 12 + accounts/tests.py | 3 + accounts/urls.py | 32 + accounts/views.py | 329 + issue/__init__.py | 1 - issue/admin.py | 13 - issue/apps.py | 10 - issue/fixtures/test_no_project.json | 1 - issue/fixtures/test_perms.json | 1 - issue/forms.py | 80 - issue/migrations/0001_initial.py | 230 - issue/migrations/0002_auto_20140823_0532.py | 24 - issue/templates/403.html | 19 - issue/templates/404.html | 19 - issue/templates/500.html | 19 - issue/templates/base.html | 123 - issue/templates/emails/close_issue.html | 4 - issue/templates/emails/new_comment.html | 4 - issue/templates/emails/new_issue.html | 8 - issue/templates/emails/reopen_issue.html | 4 - .../issue/global_permission_edit.html | 29 - .../issue/global_permission_list.html | 89 - issue/templates/issue/issue_comment.html | 17 - issue/templates/issue/issue_edit.html | 43 - issue/templates/issue/issue_list.html | 82 - issue/templates/issue/label_edit.html | 36 - issue/templates/issue/label_list.html | 50 - issue/templates/issue/milestone_edit.html | 40 - issue/templates/issue/milestone_list.html | 68 - issue/templates/issue/profile.html | 38 - issue/templates/issue/project.html | 36 - issue/templates/issue/project_add.html | 30 - issue/templates/issue/project_edit.html | 30 - issue/templates/issue/project_list.html | 53 - .../issue/project_permission_edit.html | 31 - .../issue/project_permission_list.html | 72 - issue/templates/issue/team.html | 49 - issue/templates/issue/team_edit.html | 38 - issue/templates/issue/team_list.html | 43 - issue/templates/login.html | 30 - issue/templatetags/issue_filters.py | 31 - issue/tests.py | 621 - issue/urls.py | 58 - permissions/__init__.py | 0 permissions/admin.py | 7 + {issue => permissions}/backends.py | 5 +- {issue => permissions}/context_processors.py | 10 +- {issue => permissions}/decorators.py | 6 +- permissions/forms.py | 68 + permissions/migrations/0001_initial.py | 75 + .../migrations/0002_auto_20140829_2335.py | 18 + permissions/migrations/__init__.py | 0 permissions/models.py | 144 + permissions/static/js/perm.js | 13 + permissions/templatetags/__init__.py | 0 .../templatetags/permissions_filters.py | 15 + permissions/templatetags/permissions_tags.py | 24 + permissions/tests.py | 3 + permissions/urls.py | 17 + permissions/views.py | 131 + ponytracker/settings.py | 24 +- ponytracker/urls.py | 14 +- .../static => static}/css/bootstrap-theme.css | 0 .../css/bootstrap-theme.css.map | 0 .../css/bootstrap-theme.min.css | 0 {issue/static => static}/css/bootstrap.css | 0 .../static => static}/css/bootstrap.css.map | 0 .../static => static}/css/bootstrap.min.css | 0 .../issue.css => static/css/ponytracker.css | 0 .../fonts/glyphicons-halflings-regular.eot | Bin .../fonts/glyphicons-halflings-regular.svg | 0 .../fonts/glyphicons-halflings-regular.ttf | Bin .../fonts/glyphicons-halflings-regular.woff | Bin {issue/static => static}/js/bootstrap.js | 0 {issue/static => static}/js/bootstrap.min.js | 0 static/js/jquery-ui.js | 16375 ++++++++++++++++ static/js/jquery-ui.min.js | 13 + {issue/static => static}/js/jquery.js | 0 {issue/static => static}/js/jquery.min.js | 0 {issue/static => static}/js/jquery.min.map | 0 static/js/ponytracker.js | 4 + templates/403.html | 13 + templates/404.html | 13 + templates/500.html | 13 + templates/accounts/group_details.html | 76 + templates/accounts/group_edit.html | 42 + templates/accounts/group_list.html | 31 + templates/accounts/profile.html | 74 + templates/accounts/tags/delete_modal.html | 21 + templates/accounts/team_details.html | 111 + templates/accounts/team_edit.html | 38 + templates/accounts/team_list.html | 32 + templates/accounts/user_edit.html | 38 + templates/accounts/user_list.html | 48 + templates/base.html | 105 + templates/base_project.html | 59 + templates/base_settings.html | 29 + templates/login.html | 28 + templates/permissions/global_perm_edit.html | 55 + templates/permissions/global_perm_list.html | 143 + templates/permissions/project_perm_edit.html | 54 + templates/permissions/project_perm_list.html | 92 + templates/permissions/tags/perm_form.html | 22 + templates/tracker/issue_base.html | 14 + .../tracker/issue_details.html | 9 +- templates/tracker/issue_edit.html | 37 + templates/tracker/issue_list.html | 106 + templates/tracker/label_edit.html | 33 + templates/tracker/label_list.html | 41 + templates/tracker/milestone_edit.html | 49 + templates/tracker/milestone_list.html | 59 + templates/tracker/project_add.html | 28 + templates/tracker/project_edit.html | 30 + templates/tracker/project_list.html | 43 + templates/tracker/settings.html | 9 + tracker/__init__.py | 1 + tracker/admin.py | 9 + tracker/apps.py | 10 + tracker/context_processors.py | 9 + tracker/forms.py | 44 + {issue => tracker}/middleware.py | 15 +- tracker/migrations/0001_initial.py | 131 + tracker/migrations/__init__.py | 0 {issue => tracker}/models.py | 181 +- {issue => tracker}/notifications.py | 8 +- {issue => tracker}/signals.py | 2 +- {issue => tracker}/tasks.py | 0 tracker/templatetags/__init__.py | 0 .../templatetags/tracker_tags.py | 5 - tracker/tests.py | 3 + tracker/urls.py | 45 + {issue => tracker}/views.py | 532 +- 143 files changed, 20775 insertions(+), 2764 deletions(-) rename {issue/migrations => accounts}/__init__.py (100%) create mode 100644 accounts/admin.py create mode 100644 accounts/forms.py create mode 100644 accounts/migrations/0001_initial.py rename {issue/templatetags => accounts/migrations}/__init__.py (100%) create mode 100644 accounts/models.py create mode 100644 accounts/static/css/jquery-ui.css create mode 100644 accounts/static/js/accounts.js rename issue/static/js/confirm.js => accounts/static/js/delete_modal.js (81%) create mode 100644 accounts/static/js/team.js create mode 100644 accounts/templatetags/__init__.py create mode 100644 accounts/templatetags/accounts_tags.py create mode 100644 accounts/tests.py create mode 100644 accounts/urls.py create mode 100644 accounts/views.py delete mode 100644 issue/__init__.py delete mode 100644 issue/admin.py delete mode 100644 issue/apps.py delete mode 100644 issue/fixtures/test_no_project.json delete mode 100644 issue/fixtures/test_perms.json delete mode 100644 issue/forms.py delete mode 100644 issue/migrations/0001_initial.py delete mode 100644 issue/migrations/0002_auto_20140823_0532.py delete mode 100644 issue/templates/403.html delete mode 100644 issue/templates/404.html delete mode 100644 issue/templates/500.html delete mode 100644 issue/templates/base.html delete mode 100644 issue/templates/emails/close_issue.html delete mode 100644 issue/templates/emails/new_comment.html delete mode 100644 issue/templates/emails/new_issue.html delete mode 100644 issue/templates/emails/reopen_issue.html delete mode 100644 issue/templates/issue/global_permission_edit.html delete mode 100644 issue/templates/issue/global_permission_list.html delete mode 100644 issue/templates/issue/issue_comment.html delete mode 100644 issue/templates/issue/issue_edit.html delete mode 100644 issue/templates/issue/issue_list.html delete mode 100644 issue/templates/issue/label_edit.html delete mode 100644 issue/templates/issue/label_list.html delete mode 100644 issue/templates/issue/milestone_edit.html delete mode 100644 issue/templates/issue/milestone_list.html delete mode 100644 issue/templates/issue/profile.html delete mode 100644 issue/templates/issue/project.html delete mode 100644 issue/templates/issue/project_add.html delete mode 100644 issue/templates/issue/project_edit.html delete mode 100644 issue/templates/issue/project_list.html delete mode 100644 issue/templates/issue/project_permission_edit.html delete mode 100644 issue/templates/issue/project_permission_list.html delete mode 100644 issue/templates/issue/team.html delete mode 100644 issue/templates/issue/team_edit.html delete mode 100644 issue/templates/issue/team_list.html delete mode 100644 issue/templates/login.html delete mode 100644 issue/templatetags/issue_filters.py delete mode 100644 issue/tests.py delete mode 100644 issue/urls.py create mode 100644 permissions/__init__.py create mode 100644 permissions/admin.py rename {issue => permissions}/backends.py (86%) rename {issue => permissions}/context_processors.py (76%) rename {issue => permissions}/decorators.py (95%) create mode 100644 permissions/forms.py create mode 100644 permissions/migrations/0001_initial.py create mode 100644 permissions/migrations/0002_auto_20140829_2335.py create mode 100644 permissions/migrations/__init__.py create mode 100644 permissions/models.py create mode 100644 permissions/static/js/perm.js create mode 100644 permissions/templatetags/__init__.py create mode 100644 permissions/templatetags/permissions_filters.py create mode 100644 permissions/templatetags/permissions_tags.py create mode 100644 permissions/tests.py create mode 100644 permissions/urls.py create mode 100644 permissions/views.py rename {issue/static => static}/css/bootstrap-theme.css (100%) rename {issue/static => static}/css/bootstrap-theme.css.map (100%) rename {issue/static => static}/css/bootstrap-theme.min.css (100%) rename {issue/static => static}/css/bootstrap.css (100%) rename {issue/static => static}/css/bootstrap.css.map (100%) rename {issue/static => static}/css/bootstrap.min.css (100%) rename issue/static/css/issue.css => static/css/ponytracker.css (100%) rename {issue/static => static}/fonts/glyphicons-halflings-regular.eot (100%) rename {issue/static => static}/fonts/glyphicons-halflings-regular.svg (100%) rename {issue/static => static}/fonts/glyphicons-halflings-regular.ttf (100%) rename {issue/static => static}/fonts/glyphicons-halflings-regular.woff (100%) rename {issue/static => static}/js/bootstrap.js (100%) rename {issue/static => static}/js/bootstrap.min.js (100%) create mode 100644 static/js/jquery-ui.js create mode 100644 static/js/jquery-ui.min.js rename {issue/static => static}/js/jquery.js (100%) rename {issue/static => static}/js/jquery.min.js (100%) rename {issue/static => static}/js/jquery.min.map (100%) create mode 100644 static/js/ponytracker.js create mode 100644 templates/403.html create mode 100644 templates/404.html create mode 100644 templates/500.html create mode 100644 templates/accounts/group_details.html create mode 100644 templates/accounts/group_edit.html create mode 100644 templates/accounts/group_list.html create mode 100644 templates/accounts/profile.html create mode 100644 templates/accounts/tags/delete_modal.html create mode 100644 templates/accounts/team_details.html create mode 100644 templates/accounts/team_edit.html create mode 100644 templates/accounts/team_list.html create mode 100644 templates/accounts/user_edit.html create mode 100644 templates/accounts/user_list.html create mode 100644 templates/base.html create mode 100644 templates/base_project.html create mode 100644 templates/base_settings.html create mode 100644 templates/login.html create mode 100644 templates/permissions/global_perm_edit.html create mode 100644 templates/permissions/global_perm_list.html create mode 100644 templates/permissions/project_perm_edit.html create mode 100644 templates/permissions/project_perm_list.html create mode 100644 templates/permissions/tags/perm_form.html create mode 100644 templates/tracker/issue_base.html rename issue/templates/issue/issue.html => templates/tracker/issue_details.html (98%) create mode 100644 templates/tracker/issue_edit.html create mode 100644 templates/tracker/issue_list.html create mode 100644 templates/tracker/label_edit.html create mode 100644 templates/tracker/label_list.html create mode 100644 templates/tracker/milestone_edit.html create mode 100644 templates/tracker/milestone_list.html create mode 100644 templates/tracker/project_add.html create mode 100644 templates/tracker/project_edit.html create mode 100644 templates/tracker/project_list.html create mode 100644 templates/tracker/settings.html create mode 100644 tracker/__init__.py create mode 100644 tracker/admin.py create mode 100644 tracker/apps.py create mode 100644 tracker/context_processors.py create mode 100644 tracker/forms.py rename {issue => tracker}/middleware.py (88%) create mode 100644 tracker/migrations/0001_initial.py create mode 100644 tracker/migrations/__init__.py rename {issue => tracker}/models.py (67%) rename {issue => tracker}/notifications.py (94%) rename {issue => tracker}/signals.py (92%) rename {issue => tracker}/tasks.py (100%) create mode 100644 tracker/templatetags/__init__.py rename issue/templatetags/issue_tags.py => tracker/templatetags/tracker_tags.py (91%) create mode 100644 tracker/tests.py create mode 100644 tracker/urls.py rename {issue => tracker}/views.py (66%) diff --git a/issue/migrations/__init__.py b/accounts/__init__.py similarity index 100% rename from issue/migrations/__init__.py rename to accounts/__init__.py diff --git a/accounts/admin.py b/accounts/admin.py new file mode 100644 index 0000000..8e2a570 --- /dev/null +++ b/accounts/admin.py @@ -0,0 +1,7 @@ +from django.contrib import admin + +from accounts.models import * + + +admin.site.register(User) +admin.site.register(Team) diff --git a/accounts/forms.py b/accounts/forms.py new file mode 100644 index 0000000..a766905 --- /dev/null +++ b/accounts/forms.py @@ -0,0 +1,17 @@ +from django.forms.models import modelform_factory +from django.forms.widgets import PasswordInput + +from accounts.models import * + + +__all__ = [ 'UserForm', 'GroupForm', 'TeamForm' ] + + +UserForm = modelform_factory(User, + fields=['username', 'first_name', 'last_name', + 'password', 'email', 'is_superuser'], + widgets={'password': PasswordInput}) +GroupForm = modelform_factory(Group, + fields=['name']) +TeamForm = modelform_factory(Team, + fields=['name']) diff --git a/accounts/migrations/0001_initial.py b/accounts/migrations/0001_initial.py new file mode 100644 index 0000000..1ef0452 --- /dev/null +++ b/accounts/migrations/0001_initial.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +from django.conf import settings +import django.core.validators +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('auth', '0001_initial'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='User', + fields=[ + ('id', models.AutoField(serialize=False, verbose_name='ID', primary_key=True, auto_created=True)), + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(default=django.utils.timezone.now, verbose_name='last login')), + ('is_superuser', models.BooleanField(default=False, verbose_name='superuser status', help_text='Designates that this user has all permissions without explicitly assigning them.')), + ('username', models.CharField(verbose_name='username', validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username.', 'invalid')], help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=30, unique=True)), + ('first_name', models.CharField(max_length=30, verbose_name='first name', blank=True)), + ('last_name', models.CharField(max_length=30, verbose_name='last name', blank=True)), + ('email', models.EmailField(max_length=75, verbose_name='email address', blank=True)), + ('is_staff', models.BooleanField(default=False, verbose_name='staff status', help_text='Designates whether the user can log into this admin site.')), + ('is_active', models.BooleanField(default=True, verbose_name='active', help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.')), + ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), + ('groups', models.ManyToManyField(to='auth.Group', verbose_name='groups', blank=True)), + ('user_permissions', models.ManyToManyField(to='auth.Permission', verbose_name='user permissions', blank=True)), + ], + options={ + 'ordering': ['username'], + }, + bases=(models.Model,), + ), + migrations.CreateModel( + name='Team', + fields=[ + ('id', models.AutoField(serialize=False, verbose_name='ID', primary_key=True, auto_created=True)), + ('name', models.CharField(max_length=128, unique=True)), + ('users', models.ManyToManyField(null=True, to=settings.AUTH_USER_MODEL, blank=True)), + ], + options={ + 'ordering': ['name'], + }, + bases=(models.Model,), + ), + migrations.CreateModel( + name='Group', + fields=[ + ], + options={ + 'ordering': ['name'], + 'proxy': True, + }, + bases=('auth.group',), + ), + migrations.AddField( + model_name='team', + name='groups', + field=models.ManyToManyField(null=True, to='accounts.Group', blank=True), + preserve_default=True, + ), + ] diff --git a/issue/templatetags/__init__.py b/accounts/migrations/__init__.py similarity index 100% rename from issue/templatetags/__init__.py rename to accounts/migrations/__init__.py diff --git a/accounts/models.py b/accounts/models.py new file mode 100644 index 0000000..e09e527 --- /dev/null +++ b/accounts/models.py @@ -0,0 +1,72 @@ +from django.db import models +from django.db.models import Q +from django.contrib.auth.models import AbstractUser +from django.contrib import auth +from django.utils.encoding import python_2_unicode_compatible + + +__all__ = [ 'User', 'Group', 'Team' ] + + +@python_2_unicode_compatible +class User(AbstractUser): + + class Meta: + ordering = [ 'username' ] + + @property + def teams(self): + query = Q(groups__in=self.groups.all()) | Q(users=self) + return Team.objects.filter(query).distinct() + + @property + def username_and_fullname(self): + fullname = self.fullname + if fullname: + return "%s (%s)" % (self.username, fullname) + else: + return self.username + + @property + def fullname(self): + fullname = '' + if self.first_name: + fullname += self.first_name + if self.last_name: + if fullname: + fullname += ' ' + fullname += self.last_name + return fullname + + def __str__(self): + return self.username + + +class Group(auth.models.Group): + + class Meta: + ordering = [ 'name' ] + proxy = True + + @property + def users(self): + return User.objects.filter(groups=self) + + +@python_2_unicode_compatible +class Team(models.Model): + + class Meta: + ordering = [ 'name' ] + + name = models.CharField(max_length=128, unique=True) + + # We dont want related field on User object because we use + # a special function that retrieve also team through group + users = models.ManyToManyField(User, blank=True, null=True, + related_name='+') + groups = models.ManyToManyField(Group, blank=True, null=True, + related_name='teams') + + def __str__(self): + return self.name diff --git a/accounts/static/css/jquery-ui.css b/accounts/static/css/jquery-ui.css new file mode 100644 index 0000000..9028aa2 --- /dev/null +++ b/accounts/static/css/jquery-ui.css @@ -0,0 +1,1225 @@ +/*! jQuery UI - v1.11.1 - 2014-08-13 +* http://jqueryui.com +* Includes: core.css, accordion.css, autocomplete.css, button.css, datepicker.css, dialog.css, draggable.css, menu.css, progressbar.css, resizable.css, selectable.css, selectmenu.css, slider.css, sortable.css, spinner.css, tabs.css, tooltip.css, theme.css +* To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Verdana%2CArial%2Csans-serif&fwDefault=normal&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=cccccc&bgTextureHeader=highlight_soft&bgImgOpacityHeader=75&borderColorHeader=aaaaaa&fcHeader=222222&iconColorHeader=222222&bgColorContent=ffffff&bgTextureContent=flat&bgImgOpacityContent=75&borderColorContent=aaaaaa&fcContent=222222&iconColorContent=222222&bgColorDefault=e6e6e6&bgTextureDefault=glass&bgImgOpacityDefault=75&borderColorDefault=d3d3d3&fcDefault=555555&iconColorDefault=888888&bgColorHover=dadada&bgTextureHover=glass&bgImgOpacityHover=75&borderColorHover=999999&fcHover=212121&iconColorHover=454545&bgColorActive=ffffff&bgTextureActive=glass&bgImgOpacityActive=65&borderColorActive=aaaaaa&fcActive=212121&iconColorActive=454545&bgColorHighlight=fbf9ee&bgTextureHighlight=glass&bgImgOpacityHighlight=55&borderColorHighlight=fcefa1&fcHighlight=363636&iconColorHighlight=2e83ff&bgColorError=fef1ec&bgTextureError=glass&bgImgOpacityError=95&borderColorError=cd0a0a&fcError=cd0a0a&iconColorError=cd0a0a&bgColorOverlay=aaaaaa&bgTextureOverlay=flat&bgImgOpacityOverlay=0&opacityOverlay=30&bgColorShadow=aaaaaa&bgTextureShadow=flat&bgImgOpacityShadow=0&opacityShadow=30&thicknessShadow=8px&offsetTopShadow=-8px&offsetLeftShadow=-8px&cornerRadiusShadow=8px +* Copyright 2014 jQuery Foundation and other contributors; Licensed MIT */ + +/* Layout helpers +----------------------------------*/ +.ui-helper-hidden { + display: none; +} +.ui-helper-hidden-accessible { + border: 0; + clip: rect(0 0 0 0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; +} +.ui-helper-reset { + margin: 0; + padding: 0; + border: 0; + outline: 0; + line-height: 1.3; + text-decoration: none; + font-size: 100%; + list-style: none; +} +.ui-helper-clearfix:before, +.ui-helper-clearfix:after { + content: ""; + display: table; + border-collapse: collapse; +} +.ui-helper-clearfix:after { + clear: both; +} +.ui-helper-clearfix { + min-height: 0; /* support: IE7 */ +} +.ui-helper-zfix { + width: 100%; + height: 100%; + top: 0; + left: 0; + position: absolute; + opacity: 0; + filter:Alpha(Opacity=0); /* support: IE8 */ +} + +.ui-front { + z-index: 100; +} + + +/* Interaction Cues +----------------------------------*/ +.ui-state-disabled { + cursor: default !important; +} + + +/* Icons +----------------------------------*/ + +/* states and images */ +.ui-icon { + display: block; + text-indent: -99999px; + overflow: hidden; + background-repeat: no-repeat; +} + + +/* Misc visuals +----------------------------------*/ + +/* Overlays */ +.ui-widget-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; +} +.ui-accordion .ui-accordion-header { + display: block; + cursor: pointer; + position: relative; + margin: 2px 0 0 0; + padding: .5em .5em .5em .7em; + min-height: 0; /* support: IE7 */ + font-size: 100%; +} +.ui-accordion .ui-accordion-icons { + padding-left: 2.2em; +} +.ui-accordion .ui-accordion-icons .ui-accordion-icons { + padding-left: 2.2em; +} +.ui-accordion .ui-accordion-header .ui-accordion-header-icon { + position: absolute; + left: .5em; + top: 50%; + margin-top: -8px; +} +.ui-accordion .ui-accordion-content { + padding: 1em 2.2em; + border-top: 0; + overflow: auto; +} +.ui-autocomplete { + position: absolute; + top: 0; + left: 0; + cursor: default; +} +.ui-button { + display: inline-block; + position: relative; + padding: 0; + line-height: normal; + margin-right: .1em; + cursor: pointer; + vertical-align: middle; + text-align: center; + overflow: visible; /* removes extra width in IE */ +} +.ui-button, +.ui-button:link, +.ui-button:visited, +.ui-button:hover, +.ui-button:active { + text-decoration: none; +} +/* to make room for the icon, a width needs to be set here */ +.ui-button-icon-only { + width: 2.2em; +} +/* button elements seem to need a little more width */ +button.ui-button-icon-only { + width: 2.4em; +} +.ui-button-icons-only { + width: 3.4em; +} +button.ui-button-icons-only { + width: 3.7em; +} + +/* button text element */ +.ui-button .ui-button-text { + display: block; + line-height: normal; +} +.ui-button-text-only .ui-button-text { + padding: .4em 1em; +} +.ui-button-icon-only .ui-button-text, +.ui-button-icons-only .ui-button-text { + padding: .4em; + text-indent: -9999999px; +} +.ui-button-text-icon-primary .ui-button-text, +.ui-button-text-icons .ui-button-text { + padding: .4em 1em .4em 2.1em; +} +.ui-button-text-icon-secondary .ui-button-text, +.ui-button-text-icons .ui-button-text { + padding: .4em 2.1em .4em 1em; +} +.ui-button-text-icons .ui-button-text { + padding-left: 2.1em; + padding-right: 2.1em; +} +/* no icon support for input elements, provide padding by default */ +input.ui-button { + padding: .4em 1em; +} + +/* button icon element(s) */ +.ui-button-icon-only .ui-icon, +.ui-button-text-icon-primary .ui-icon, +.ui-button-text-icon-secondary .ui-icon, +.ui-button-text-icons .ui-icon, +.ui-button-icons-only .ui-icon { + position: absolute; + top: 50%; + margin-top: -8px; +} +.ui-button-icon-only .ui-icon { + left: 50%; + margin-left: -8px; +} +.ui-button-text-icon-primary .ui-button-icon-primary, +.ui-button-text-icons .ui-button-icon-primary, +.ui-button-icons-only .ui-button-icon-primary { + left: .5em; +} +.ui-button-text-icon-secondary .ui-button-icon-secondary, +.ui-button-text-icons .ui-button-icon-secondary, +.ui-button-icons-only .ui-button-icon-secondary { + right: .5em; +} + +/* button sets */ +.ui-buttonset { + margin-right: 7px; +} +.ui-buttonset .ui-button { + margin-left: 0; + margin-right: -.3em; +} + +/* workarounds */ +/* reset extra padding in Firefox, see h5bp.com/l */ +input.ui-button::-moz-focus-inner, +button.ui-button::-moz-focus-inner { + border: 0; + padding: 0; +} +.ui-datepicker { + width: 17em; + padding: .2em .2em 0; + display: none; +} +.ui-datepicker .ui-datepicker-header { + position: relative; + padding: .2em 0; +} +.ui-datepicker .ui-datepicker-prev, +.ui-datepicker .ui-datepicker-next { + position: absolute; + top: 2px; + width: 1.8em; + height: 1.8em; +} +.ui-datepicker .ui-datepicker-prev-hover, +.ui-datepicker .ui-datepicker-next-hover { + top: 1px; +} +.ui-datepicker .ui-datepicker-prev { + left: 2px; +} +.ui-datepicker .ui-datepicker-next { + right: 2px; +} +.ui-datepicker .ui-datepicker-prev-hover { + left: 1px; +} +.ui-datepicker .ui-datepicker-next-hover { + right: 1px; +} +.ui-datepicker .ui-datepicker-prev span, +.ui-datepicker .ui-datepicker-next span { + display: block; + position: absolute; + left: 50%; + margin-left: -8px; + top: 50%; + margin-top: -8px; +} +.ui-datepicker .ui-datepicker-title { + margin: 0 2.3em; + line-height: 1.8em; + text-align: center; +} +.ui-datepicker .ui-datepicker-title select { + font-size: 1em; + margin: 1px 0; +} +.ui-datepicker select.ui-datepicker-month, +.ui-datepicker select.ui-datepicker-year { + width: 45%; +} +.ui-datepicker table { + width: 100%; + font-size: .9em; + border-collapse: collapse; + margin: 0 0 .4em; +} +.ui-datepicker th { + padding: .7em .3em; + text-align: center; + font-weight: bold; + border: 0; +} +.ui-datepicker td { + border: 0; + padding: 1px; +} +.ui-datepicker td span, +.ui-datepicker td a { + display: block; + padding: .2em; + text-align: right; + text-decoration: none; +} +.ui-datepicker .ui-datepicker-buttonpane { + background-image: none; + margin: .7em 0 0 0; + padding: 0 .2em; + border-left: 0; + border-right: 0; + border-bottom: 0; +} +.ui-datepicker .ui-datepicker-buttonpane button { + float: right; + margin: .5em .2em .4em; + cursor: pointer; + padding: .2em .6em .3em .6em; + width: auto; + overflow: visible; +} +.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { + float: left; +} + +/* with multiple calendars */ +.ui-datepicker.ui-datepicker-multi { + width: auto; +} +.ui-datepicker-multi .ui-datepicker-group { + float: left; +} +.ui-datepicker-multi .ui-datepicker-group table { + width: 95%; + margin: 0 auto .4em; +} +.ui-datepicker-multi-2 .ui-datepicker-group { + width: 50%; +} +.ui-datepicker-multi-3 .ui-datepicker-group { + width: 33.3%; +} +.ui-datepicker-multi-4 .ui-datepicker-group { + width: 25%; +} +.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header, +.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { + border-left-width: 0; +} +.ui-datepicker-multi .ui-datepicker-buttonpane { + clear: left; +} +.ui-datepicker-row-break { + clear: both; + width: 100%; + font-size: 0; +} + +/* RTL support */ +.ui-datepicker-rtl { + direction: rtl; +} +.ui-datepicker-rtl .ui-datepicker-prev { + right: 2px; + left: auto; +} +.ui-datepicker-rtl .ui-datepicker-next { + left: 2px; + right: auto; +} +.ui-datepicker-rtl .ui-datepicker-prev:hover { + right: 1px; + left: auto; +} +.ui-datepicker-rtl .ui-datepicker-next:hover { + left: 1px; + right: auto; +} +.ui-datepicker-rtl .ui-datepicker-buttonpane { + clear: right; +} +.ui-datepicker-rtl .ui-datepicker-buttonpane button { + float: left; +} +.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current, +.ui-datepicker-rtl .ui-datepicker-group { + float: right; +} +.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header, +.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { + border-right-width: 0; + border-left-width: 1px; +} +.ui-dialog { + overflow: hidden; + position: absolute; + top: 0; + left: 0; + padding: .2em; + outline: 0; +} +.ui-dialog .ui-dialog-titlebar { + padding: .4em 1em; + position: relative; +} +.ui-dialog .ui-dialog-title { + float: left; + margin: .1em 0; + white-space: nowrap; + width: 90%; + overflow: hidden; + text-overflow: ellipsis; +} +.ui-dialog .ui-dialog-titlebar-close { + position: absolute; + right: .3em; + top: 50%; + width: 20px; + margin: -10px 0 0 0; + padding: 1px; + height: 20px; +} +.ui-dialog .ui-dialog-content { + position: relative; + border: 0; + padding: .5em 1em; + background: none; + overflow: auto; +} +.ui-dialog .ui-dialog-buttonpane { + text-align: left; + border-width: 1px 0 0 0; + background-image: none; + margin-top: .5em; + padding: .3em 1em .5em .4em; +} +.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { + float: right; +} +.ui-dialog .ui-dialog-buttonpane button { + margin: .5em .4em .5em 0; + cursor: pointer; +} +.ui-dialog .ui-resizable-se { + width: 12px; + height: 12px; + right: -5px; + bottom: -5px; + background-position: 16px 16px; +} +.ui-draggable .ui-dialog-titlebar { + cursor: move; +} +.ui-draggable-handle { + -ms-touch-action: none; + touch-action: none; +} +.ui-menu { + list-style: none; + padding: 0; + margin: 0; + display: block; + outline: none; +} +.ui-menu .ui-menu { + position: absolute; +} +.ui-menu .ui-menu-item { + position: relative; + margin: 0; + padding: 3px 1em 3px .4em; + cursor: pointer; + min-height: 0; /* support: IE7 */ + /* support: IE10, see #8844 */ + list-style-image: url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"); +} +.ui-menu .ui-menu-divider { + margin: 5px 0; + height: 0; + font-size: 0; + line-height: 0; + border-width: 1px 0 0 0; +} +.ui-menu .ui-state-focus, +.ui-menu .ui-state-active { + margin: -1px; +} + +/* icon support */ +.ui-menu-icons { + position: relative; +} +.ui-menu-icons .ui-menu-item { + padding-left: 2em; +} + +/* left-aligned */ +.ui-menu .ui-icon { + position: absolute; + top: 0; + bottom: 0; + left: .2em; + margin: auto 0; +} + +/* right-aligned */ +.ui-menu .ui-menu-icon { + left: auto; + right: 0; +} +.ui-progressbar { + height: 2em; + text-align: left; + overflow: hidden; +} +.ui-progressbar .ui-progressbar-value { + margin: -1px; + height: 100%; +} +.ui-progressbar .ui-progressbar-overlay { + background: url("data:image/gif;base64,R0lGODlhKAAoAIABAAAAAP///yH/C05FVFNDQVBFMi4wAwEAAAAh+QQJAQABACwAAAAAKAAoAAACkYwNqXrdC52DS06a7MFZI+4FHBCKoDeWKXqymPqGqxvJrXZbMx7Ttc+w9XgU2FB3lOyQRWET2IFGiU9m1frDVpxZZc6bfHwv4c1YXP6k1Vdy292Fb6UkuvFtXpvWSzA+HycXJHUXiGYIiMg2R6W459gnWGfHNdjIqDWVqemH2ekpObkpOlppWUqZiqr6edqqWQAAIfkECQEAAQAsAAAAACgAKAAAApSMgZnGfaqcg1E2uuzDmmHUBR8Qil95hiPKqWn3aqtLsS18y7G1SzNeowWBENtQd+T1JktP05nzPTdJZlR6vUxNWWjV+vUWhWNkWFwxl9VpZRedYcflIOLafaa28XdsH/ynlcc1uPVDZxQIR0K25+cICCmoqCe5mGhZOfeYSUh5yJcJyrkZWWpaR8doJ2o4NYq62lAAACH5BAkBAAEALAAAAAAoACgAAAKVDI4Yy22ZnINRNqosw0Bv7i1gyHUkFj7oSaWlu3ovC8GxNso5fluz3qLVhBVeT/Lz7ZTHyxL5dDalQWPVOsQWtRnuwXaFTj9jVVh8pma9JjZ4zYSj5ZOyma7uuolffh+IR5aW97cHuBUXKGKXlKjn+DiHWMcYJah4N0lYCMlJOXipGRr5qdgoSTrqWSq6WFl2ypoaUAAAIfkECQEAAQAsAAAAACgAKAAAApaEb6HLgd/iO7FNWtcFWe+ufODGjRfoiJ2akShbueb0wtI50zm02pbvwfWEMWBQ1zKGlLIhskiEPm9R6vRXxV4ZzWT2yHOGpWMyorblKlNp8HmHEb/lCXjcW7bmtXP8Xt229OVWR1fod2eWqNfHuMjXCPkIGNileOiImVmCOEmoSfn3yXlJWmoHGhqp6ilYuWYpmTqKUgAAIfkECQEAAQAsAAAAACgAKAAAApiEH6kb58biQ3FNWtMFWW3eNVcojuFGfqnZqSebuS06w5V80/X02pKe8zFwP6EFWOT1lDFk8rGERh1TTNOocQ61Hm4Xm2VexUHpzjymViHrFbiELsefVrn6XKfnt2Q9G/+Xdie499XHd2g4h7ioOGhXGJboGAnXSBnoBwKYyfioubZJ2Hn0RuRZaflZOil56Zp6iioKSXpUAAAh+QQJAQABACwAAAAAKAAoAAACkoQRqRvnxuI7kU1a1UU5bd5tnSeOZXhmn5lWK3qNTWvRdQxP8qvaC+/yaYQzXO7BMvaUEmJRd3TsiMAgswmNYrSgZdYrTX6tSHGZO73ezuAw2uxuQ+BbeZfMxsexY35+/Qe4J1inV0g4x3WHuMhIl2jXOKT2Q+VU5fgoSUI52VfZyfkJGkha6jmY+aaYdirq+lQAACH5BAkBAAEALAAAAAAoACgAAAKWBIKpYe0L3YNKToqswUlvznigd4wiR4KhZrKt9Upqip61i9E3vMvxRdHlbEFiEXfk9YARYxOZZD6VQ2pUunBmtRXo1Lf8hMVVcNl8JafV38aM2/Fu5V16Bn63r6xt97j09+MXSFi4BniGFae3hzbH9+hYBzkpuUh5aZmHuanZOZgIuvbGiNeomCnaxxap2upaCZsq+1kAACH5BAkBAAEALAAAAAAoACgAAAKXjI8By5zf4kOxTVrXNVlv1X0d8IGZGKLnNpYtm8Lr9cqVeuOSvfOW79D9aDHizNhDJidFZhNydEahOaDH6nomtJjp1tutKoNWkvA6JqfRVLHU/QUfau9l2x7G54d1fl995xcIGAdXqMfBNadoYrhH+Mg2KBlpVpbluCiXmMnZ2Sh4GBqJ+ckIOqqJ6LmKSllZmsoq6wpQAAAh+QQJAQABACwAAAAAKAAoAAAClYx/oLvoxuJDkU1a1YUZbJ59nSd2ZXhWqbRa2/gF8Gu2DY3iqs7yrq+xBYEkYvFSM8aSSObE+ZgRl1BHFZNr7pRCavZ5BW2142hY3AN/zWtsmf12p9XxxFl2lpLn1rseztfXZjdIWIf2s5dItwjYKBgo9yg5pHgzJXTEeGlZuenpyPmpGQoKOWkYmSpaSnqKileI2FAAACH5BAkBAAEALAAAAAAoACgAAAKVjB+gu+jG4kORTVrVhRlsnn2dJ3ZleFaptFrb+CXmO9OozeL5VfP99HvAWhpiUdcwkpBH3825AwYdU8xTqlLGhtCosArKMpvfa1mMRae9VvWZfeB2XfPkeLmm18lUcBj+p5dnN8jXZ3YIGEhYuOUn45aoCDkp16hl5IjYJvjWKcnoGQpqyPlpOhr3aElaqrq56Bq7VAAAOw=="); + height: 100%; + filter: alpha(opacity=25); /* support: IE8 */ + opacity: 0.25; +} +.ui-progressbar-indeterminate .ui-progressbar-value { + background-image: none; +} +.ui-resizable { + position: relative; +} +.ui-resizable-handle { + position: absolute; + font-size: 0.1px; + display: block; + -ms-touch-action: none; + touch-action: none; +} +.ui-resizable-disabled .ui-resizable-handle, +.ui-resizable-autohide .ui-resizable-handle { + display: none; +} +.ui-resizable-n { + cursor: n-resize; + height: 7px; + width: 100%; + top: -5px; + left: 0; +} +.ui-resizable-s { + cursor: s-resize; + height: 7px; + width: 100%; + bottom: -5px; + left: 0; +} +.ui-resizable-e { + cursor: e-resize; + width: 7px; + right: -5px; + top: 0; + height: 100%; +} +.ui-resizable-w { + cursor: w-resize; + width: 7px; + left: -5px; + top: 0; + height: 100%; +} +.ui-resizable-se { + cursor: se-resize; + width: 12px; + height: 12px; + right: 1px; + bottom: 1px; +} +.ui-resizable-sw { + cursor: sw-resize; + width: 9px; + height: 9px; + left: -5px; + bottom: -5px; +} +.ui-resizable-nw { + cursor: nw-resize; + width: 9px; + height: 9px; + left: -5px; + top: -5px; +} +.ui-resizable-ne { + cursor: ne-resize; + width: 9px; + height: 9px; + right: -5px; + top: -5px; +} +.ui-selectable { + -ms-touch-action: none; + touch-action: none; +} +.ui-selectable-helper { + position: absolute; + z-index: 100; + border: 1px dotted black; +} +.ui-selectmenu-menu { + padding: 0; + margin: 0; + position: absolute; + top: 0; + left: 0; + display: none; +} +.ui-selectmenu-menu .ui-menu { + overflow: auto; + /* Support: IE7 */ + overflow-x: hidden; + padding-bottom: 1px; +} +.ui-selectmenu-menu .ui-menu .ui-selectmenu-optgroup { + font-size: 1em; + font-weight: bold; + line-height: 1.5; + padding: 2px 0.4em; + margin: 0.5em 0 0 0; + height: auto; + border: 0; +} +.ui-selectmenu-open { + display: block; +} +.ui-selectmenu-button { + display: inline-block; + overflow: hidden; + position: relative; + text-decoration: none; + cursor: pointer; +} +.ui-selectmenu-button span.ui-icon { + right: 0.5em; + left: auto; + margin-top: -8px; + position: absolute; + top: 50%; +} +.ui-selectmenu-button span.ui-selectmenu-text { + text-align: left; + padding: 0.4em 2.1em 0.4em 1em; + display: block; + line-height: 1.4; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.ui-slider { + position: relative; + text-align: left; +} +.ui-slider .ui-slider-handle { + position: absolute; + z-index: 2; + width: 1.2em; + height: 1.2em; + cursor: default; + -ms-touch-action: none; + touch-action: none; +} +.ui-slider .ui-slider-range { + position: absolute; + z-index: 1; + font-size: .7em; + display: block; + border: 0; + background-position: 0 0; +} + +/* support: IE8 - See #6727 */ +.ui-slider.ui-state-disabled .ui-slider-handle, +.ui-slider.ui-state-disabled .ui-slider-range { + filter: inherit; +} + +.ui-slider-horizontal { + height: .8em; +} +.ui-slider-horizontal .ui-slider-handle { + top: -.3em; + margin-left: -.6em; +} +.ui-slider-horizontal .ui-slider-range { + top: 0; + height: 100%; +} +.ui-slider-horizontal .ui-slider-range-min { + left: 0; +} +.ui-slider-horizontal .ui-slider-range-max { + right: 0; +} + +.ui-slider-vertical { + width: .8em; + height: 100px; +} +.ui-slider-vertical .ui-slider-handle { + left: -.3em; + margin-left: 0; + margin-bottom: -.6em; +} +.ui-slider-vertical .ui-slider-range { + left: 0; + width: 100%; +} +.ui-slider-vertical .ui-slider-range-min { + bottom: 0; +} +.ui-slider-vertical .ui-slider-range-max { + top: 0; +} +.ui-sortable-handle { + -ms-touch-action: none; + touch-action: none; +} +.ui-spinner { + position: relative; + display: inline-block; + overflow: hidden; + padding: 0; + vertical-align: middle; +} +.ui-spinner-input { + border: none; + background: none; + color: inherit; + padding: 0; + margin: .2em 0; + vertical-align: middle; + margin-left: .4em; + margin-right: 22px; +} +.ui-spinner-button { + width: 16px; + height: 50%; + font-size: .5em; + padding: 0; + margin: 0; + text-align: center; + position: absolute; + cursor: default; + display: block; + overflow: hidden; + right: 0; +} +/* more specificity required here to override default borders */ +.ui-spinner a.ui-spinner-button { + border-top: none; + border-bottom: none; + border-right: none; +} +/* vertically center icon */ +.ui-spinner .ui-icon { + position: absolute; + margin-top: -8px; + top: 50%; + left: 0; +} +.ui-spinner-up { + top: 0; +} +.ui-spinner-down { + bottom: 0; +} + +/* TR overrides */ +.ui-spinner .ui-icon-triangle-1-s { + /* need to fix icons sprite */ + background-position: -65px -16px; +} +.ui-tabs { + position: relative;/* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */ + padding: .2em; +} +.ui-tabs .ui-tabs-nav { + margin: 0; + padding: .2em .2em 0; +} +.ui-tabs .ui-tabs-nav li { + list-style: none; + float: left; + position: relative; + top: 0; + margin: 1px .2em 0 0; + border-bottom-width: 0; + padding: 0; + white-space: nowrap; +} +.ui-tabs .ui-tabs-nav .ui-tabs-anchor { + float: left; + padding: .5em 1em; + text-decoration: none; +} +.ui-tabs .ui-tabs-nav li.ui-tabs-active { + margin-bottom: -1px; + padding-bottom: 1px; +} +.ui-tabs .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor, +.ui-tabs .ui-tabs-nav li.ui-state-disabled .ui-tabs-anchor, +.ui-tabs .ui-tabs-nav li.ui-tabs-loading .ui-tabs-anchor { + cursor: text; +} +.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor { + cursor: pointer; +} +.ui-tabs .ui-tabs-panel { + display: block; + border-width: 0; + padding: 1em 1.4em; + background: none; +} +.ui-tooltip { + padding: 8px; + position: absolute; + z-index: 9999; + max-width: 300px; + -webkit-box-shadow: 0 0 5px #aaa; + box-shadow: 0 0 5px #aaa; +} +body .ui-tooltip { + border-width: 2px; +} + +/* Component containers +----------------------------------*/ +.ui-widget { + font-family: Verdana,Arial,sans-serif; + font-size: 1.1em; +} +.ui-widget .ui-widget { + font-size: 1em; +} +.ui-widget input, +.ui-widget select, +.ui-widget textarea, +.ui-widget button { + font-family: Verdana,Arial,sans-serif; + font-size: 1em; +} +.ui-widget-content { + border: 1px solid #aaaaaa; + background: #ffffff url("images/ui-bg_flat_75_ffffff_40x100.png") 50% 50% repeat-x; + color: #222222; +} +.ui-widget-content a { + color: #222222; +} +.ui-widget-header { + border: 1px solid #aaaaaa; + background: #cccccc url("images/ui-bg_highlight-soft_75_cccccc_1x100.png") 50% 50% repeat-x; + color: #222222; + font-weight: bold; +} +.ui-widget-header a { + color: #222222; +} + +/* Interaction states +----------------------------------*/ +.ui-state-default, +.ui-widget-content .ui-state-default, +.ui-widget-header .ui-state-default { + border: 1px solid #d3d3d3; + background: #e6e6e6 url("images/ui-bg_glass_75_e6e6e6_1x400.png") 50% 50% repeat-x; + font-weight: normal; + color: #555555; +} +.ui-state-default a, +.ui-state-default a:link, +.ui-state-default a:visited { + color: #555555; + text-decoration: none; +} +.ui-state-hover, +.ui-widget-content .ui-state-hover, +.ui-widget-header .ui-state-hover, +.ui-state-focus, +.ui-widget-content .ui-state-focus, +.ui-widget-header .ui-state-focus { + border: 1px solid #999999; + background: #dadada url("images/ui-bg_glass_75_dadada_1x400.png") 50% 50% repeat-x; + font-weight: normal; + color: #212121; +} +.ui-state-hover a, +.ui-state-hover a:hover, +.ui-state-hover a:link, +.ui-state-hover a:visited, +.ui-state-focus a, +.ui-state-focus a:hover, +.ui-state-focus a:link, +.ui-state-focus a:visited { + color: #212121; + text-decoration: none; +} +.ui-state-active, +.ui-widget-content .ui-state-active, +.ui-widget-header .ui-state-active { + border: 1px solid #aaaaaa; + background: #ffffff url("images/ui-bg_glass_65_ffffff_1x400.png") 50% 50% repeat-x; + font-weight: normal; + color: #212121; +} +.ui-state-active a, +.ui-state-active a:link, +.ui-state-active a:visited { + color: #212121; + text-decoration: none; +} + +/* Interaction Cues +----------------------------------*/ +.ui-state-highlight, +.ui-widget-content .ui-state-highlight, +.ui-widget-header .ui-state-highlight { + border: 1px solid #fcefa1; + background: #fbf9ee url("images/ui-bg_glass_55_fbf9ee_1x400.png") 50% 50% repeat-x; + color: #363636; +} +.ui-state-highlight a, +.ui-widget-content .ui-state-highlight a, +.ui-widget-header .ui-state-highlight a { + color: #363636; +} +.ui-state-error, +.ui-widget-content .ui-state-error, +.ui-widget-header .ui-state-error { + border: 1px solid #cd0a0a; + background: #fef1ec url("images/ui-bg_glass_95_fef1ec_1x400.png") 50% 50% repeat-x; + color: #cd0a0a; +} +.ui-state-error a, +.ui-widget-content .ui-state-error a, +.ui-widget-header .ui-state-error a { + color: #cd0a0a; +} +.ui-state-error-text, +.ui-widget-content .ui-state-error-text, +.ui-widget-header .ui-state-error-text { + color: #cd0a0a; +} +.ui-priority-primary, +.ui-widget-content .ui-priority-primary, +.ui-widget-header .ui-priority-primary { + font-weight: bold; +} +.ui-priority-secondary, +.ui-widget-content .ui-priority-secondary, +.ui-widget-header .ui-priority-secondary { + opacity: .7; + filter:Alpha(Opacity=70); /* support: IE8 */ + font-weight: normal; +} +.ui-state-disabled, +.ui-widget-content .ui-state-disabled, +.ui-widget-header .ui-state-disabled { + opacity: .35; + filter:Alpha(Opacity=35); /* support: IE8 */ + background-image: none; +} +.ui-state-disabled .ui-icon { + filter:Alpha(Opacity=35); /* support: IE8 - See #6059 */ +} + +/* Icons +----------------------------------*/ + +/* states and images */ +.ui-icon { + width: 16px; + height: 16px; +} +.ui-icon, +.ui-widget-content .ui-icon { + background-image: url("images/ui-icons_222222_256x240.png"); +} +.ui-widget-header .ui-icon { + background-image: url("images/ui-icons_222222_256x240.png"); +} +.ui-state-default .ui-icon { + background-image: url("images/ui-icons_888888_256x240.png"); +} +.ui-state-hover .ui-icon, +.ui-state-focus .ui-icon { + background-image: url("images/ui-icons_454545_256x240.png"); +} +.ui-state-active .ui-icon { + background-image: url("images/ui-icons_454545_256x240.png"); +} +.ui-state-highlight .ui-icon { + background-image: url("images/ui-icons_2e83ff_256x240.png"); +} +.ui-state-error .ui-icon, +.ui-state-error-text .ui-icon { + background-image: url("images/ui-icons_cd0a0a_256x240.png"); +} + +/* positioning */ +.ui-icon-blank { background-position: 16px 16px; } +.ui-icon-carat-1-n { background-position: 0 0; } +.ui-icon-carat-1-ne { background-position: -16px 0; } +.ui-icon-carat-1-e { background-position: -32px 0; } +.ui-icon-carat-1-se { background-position: -48px 0; } +.ui-icon-carat-1-s { background-position: -64px 0; } +.ui-icon-carat-1-sw { background-position: -80px 0; } +.ui-icon-carat-1-w { background-position: -96px 0; } +.ui-icon-carat-1-nw { background-position: -112px 0; } +.ui-icon-carat-2-n-s { background-position: -128px 0; } +.ui-icon-carat-2-e-w { background-position: -144px 0; } +.ui-icon-triangle-1-n { background-position: 0 -16px; } +.ui-icon-triangle-1-ne { background-position: -16px -16px; } +.ui-icon-triangle-1-e { background-position: -32px -16px; } +.ui-icon-triangle-1-se { background-position: -48px -16px; } +.ui-icon-triangle-1-s { background-position: -64px -16px; } +.ui-icon-triangle-1-sw { background-position: -80px -16px; } +.ui-icon-triangle-1-w { background-position: -96px -16px; } +.ui-icon-triangle-1-nw { background-position: -112px -16px; } +.ui-icon-triangle-2-n-s { background-position: -128px -16px; } +.ui-icon-triangle-2-e-w { background-position: -144px -16px; } +.ui-icon-arrow-1-n { background-position: 0 -32px; } +.ui-icon-arrow-1-ne { background-position: -16px -32px; } +.ui-icon-arrow-1-e { background-position: -32px -32px; } +.ui-icon-arrow-1-se { background-position: -48px -32px; } +.ui-icon-arrow-1-s { background-position: -64px -32px; } +.ui-icon-arrow-1-sw { background-position: -80px -32px; } +.ui-icon-arrow-1-w { background-position: -96px -32px; } +.ui-icon-arrow-1-nw { background-position: -112px -32px; } +.ui-icon-arrow-2-n-s { background-position: -128px -32px; } +.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; } +.ui-icon-arrow-2-e-w { background-position: -160px -32px; } +.ui-icon-arrow-2-se-nw { background-position: -176px -32px; } +.ui-icon-arrowstop-1-n { background-position: -192px -32px; } +.ui-icon-arrowstop-1-e { background-position: -208px -32px; } +.ui-icon-arrowstop-1-s { background-position: -224px -32px; } +.ui-icon-arrowstop-1-w { background-position: -240px -32px; } +.ui-icon-arrowthick-1-n { background-position: 0 -48px; } +.ui-icon-arrowthick-1-ne { background-position: -16px -48px; } +.ui-icon-arrowthick-1-e { background-position: -32px -48px; } +.ui-icon-arrowthick-1-se { background-position: -48px -48px; } +.ui-icon-arrowthick-1-s { background-position: -64px -48px; } +.ui-icon-arrowthick-1-sw { background-position: -80px -48px; } +.ui-icon-arrowthick-1-w { background-position: -96px -48px; } +.ui-icon-arrowthick-1-nw { background-position: -112px -48px; } +.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; } +.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; } +.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; } +.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; } +.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; } +.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; } +.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; } +.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; } +.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; } +.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; } +.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; } +.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; } +.ui-icon-arrowreturn-1-w { background-position: -64px -64px; } +.ui-icon-arrowreturn-1-n { background-position: -80px -64px; } +.ui-icon-arrowreturn-1-e { background-position: -96px -64px; } +.ui-icon-arrowreturn-1-s { background-position: -112px -64px; } +.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; } +.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; } +.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; } +.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; } +.ui-icon-arrow-4 { background-position: 0 -80px; } +.ui-icon-arrow-4-diag { background-position: -16px -80px; } +.ui-icon-extlink { background-position: -32px -80px; } +.ui-icon-newwin { background-position: -48px -80px; } +.ui-icon-refresh { background-position: -64px -80px; } +.ui-icon-shuffle { background-position: -80px -80px; } +.ui-icon-transfer-e-w { background-position: -96px -80px; } +.ui-icon-transferthick-e-w { background-position: -112px -80px; } +.ui-icon-folder-collapsed { background-position: 0 -96px; } +.ui-icon-folder-open { background-position: -16px -96px; } +.ui-icon-document { background-position: -32px -96px; } +.ui-icon-document-b { background-position: -48px -96px; } +.ui-icon-note { background-position: -64px -96px; } +.ui-icon-mail-closed { background-position: -80px -96px; } +.ui-icon-mail-open { background-position: -96px -96px; } +.ui-icon-suitcase { background-position: -112px -96px; } +.ui-icon-comment { background-position: -128px -96px; } +.ui-icon-person { background-position: -144px -96px; } +.ui-icon-print { background-position: -160px -96px; } +.ui-icon-trash { background-position: -176px -96px; } +.ui-icon-locked { background-position: -192px -96px; } +.ui-icon-unlocked { background-position: -208px -96px; } +.ui-icon-bookmark { background-position: -224px -96px; } +.ui-icon-tag { background-position: -240px -96px; } +.ui-icon-home { background-position: 0 -112px; } +.ui-icon-flag { background-position: -16px -112px; } +.ui-icon-calendar { background-position: -32px -112px; } +.ui-icon-cart { background-position: -48px -112px; } +.ui-icon-pencil { background-position: -64px -112px; } +.ui-icon-clock { background-position: -80px -112px; } +.ui-icon-disk { background-position: -96px -112px; } +.ui-icon-calculator { background-position: -112px -112px; } +.ui-icon-zoomin { background-position: -128px -112px; } +.ui-icon-zoomout { background-position: -144px -112px; } +.ui-icon-search { background-position: -160px -112px; } +.ui-icon-wrench { background-position: -176px -112px; } +.ui-icon-gear { background-position: -192px -112px; } +.ui-icon-heart { background-position: -208px -112px; } +.ui-icon-star { background-position: -224px -112px; } +.ui-icon-link { background-position: -240px -112px; } +.ui-icon-cancel { background-position: 0 -128px; } +.ui-icon-plus { background-position: -16px -128px; } +.ui-icon-plusthick { background-position: -32px -128px; } +.ui-icon-minus { background-position: -48px -128px; } +.ui-icon-minusthick { background-position: -64px -128px; } +.ui-icon-close { background-position: -80px -128px; } +.ui-icon-closethick { background-position: -96px -128px; } +.ui-icon-key { background-position: -112px -128px; } +.ui-icon-lightbulb { background-position: -128px -128px; } +.ui-icon-scissors { background-position: -144px -128px; } +.ui-icon-clipboard { background-position: -160px -128px; } +.ui-icon-copy { background-position: -176px -128px; } +.ui-icon-contact { background-position: -192px -128px; } +.ui-icon-image { background-position: -208px -128px; } +.ui-icon-video { background-position: -224px -128px; } +.ui-icon-script { background-position: -240px -128px; } +.ui-icon-alert { background-position: 0 -144px; } +.ui-icon-info { background-position: -16px -144px; } +.ui-icon-notice { background-position: -32px -144px; } +.ui-icon-help { background-position: -48px -144px; } +.ui-icon-check { background-position: -64px -144px; } +.ui-icon-bullet { background-position: -80px -144px; } +.ui-icon-radio-on { background-position: -96px -144px; } +.ui-icon-radio-off { background-position: -112px -144px; } +.ui-icon-pin-w { background-position: -128px -144px; } +.ui-icon-pin-s { background-position: -144px -144px; } +.ui-icon-play { background-position: 0 -160px; } +.ui-icon-pause { background-position: -16px -160px; } +.ui-icon-seek-next { background-position: -32px -160px; } +.ui-icon-seek-prev { background-position: -48px -160px; } +.ui-icon-seek-end { background-position: -64px -160px; } +.ui-icon-seek-start { background-position: -80px -160px; } +/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */ +.ui-icon-seek-first { background-position: -80px -160px; } +.ui-icon-stop { background-position: -96px -160px; } +.ui-icon-eject { background-position: -112px -160px; } +.ui-icon-volume-off { background-position: -128px -160px; } +.ui-icon-volume-on { background-position: -144px -160px; } +.ui-icon-power { background-position: 0 -176px; } +.ui-icon-signal-diag { background-position: -16px -176px; } +.ui-icon-signal { background-position: -32px -176px; } +.ui-icon-battery-0 { background-position: -48px -176px; } +.ui-icon-battery-1 { background-position: -64px -176px; } +.ui-icon-battery-2 { background-position: -80px -176px; } +.ui-icon-battery-3 { background-position: -96px -176px; } +.ui-icon-circle-plus { background-position: 0 -192px; } +.ui-icon-circle-minus { background-position: -16px -192px; } +.ui-icon-circle-close { background-position: -32px -192px; } +.ui-icon-circle-triangle-e { background-position: -48px -192px; } +.ui-icon-circle-triangle-s { background-position: -64px -192px; } +.ui-icon-circle-triangle-w { background-position: -80px -192px; } +.ui-icon-circle-triangle-n { background-position: -96px -192px; } +.ui-icon-circle-arrow-e { background-position: -112px -192px; } +.ui-icon-circle-arrow-s { background-position: -128px -192px; } +.ui-icon-circle-arrow-w { background-position: -144px -192px; } +.ui-icon-circle-arrow-n { background-position: -160px -192px; } +.ui-icon-circle-zoomin { background-position: -176px -192px; } +.ui-icon-circle-zoomout { background-position: -192px -192px; } +.ui-icon-circle-check { background-position: -208px -192px; } +.ui-icon-circlesmall-plus { background-position: 0 -208px; } +.ui-icon-circlesmall-minus { background-position: -16px -208px; } +.ui-icon-circlesmall-close { background-position: -32px -208px; } +.ui-icon-squaresmall-plus { background-position: -48px -208px; } +.ui-icon-squaresmall-minus { background-position: -64px -208px; } +.ui-icon-squaresmall-close { background-position: -80px -208px; } +.ui-icon-grip-dotted-vertical { background-position: 0 -224px; } +.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; } +.ui-icon-grip-solid-vertical { background-position: -32px -224px; } +.ui-icon-grip-solid-horizontal { background-position: -48px -224px; } +.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; } +.ui-icon-grip-diagonal-se { background-position: -80px -224px; } + + +/* Misc visuals +----------------------------------*/ + +/* Corner radius */ +.ui-corner-all, +.ui-corner-top, +.ui-corner-left, +.ui-corner-tl { + border-top-left-radius: 4px; +} +.ui-corner-all, +.ui-corner-top, +.ui-corner-right, +.ui-corner-tr { + border-top-right-radius: 4px; +} +.ui-corner-all, +.ui-corner-bottom, +.ui-corner-left, +.ui-corner-bl { + border-bottom-left-radius: 4px; +} +.ui-corner-all, +.ui-corner-bottom, +.ui-corner-right, +.ui-corner-br { + border-bottom-right-radius: 4px; +} + +/* Overlays */ +/*.ui-widget-overlay { + background: #aaaaaa url("images/ui-bg_flat_0_aaaaaa_40x100.png") 50% 50% repeat-x; + opacity: .3; + filter: Alpha(Opacity=30); +} +.ui-widget-shadow { + margin: -8px 0 0 -8px; + padding: 8px; + background: #aaaaaa url("images/ui-bg_flat_0_aaaaaa_40x100.png") 50% 50% repeat-x; + opacity: .3; + filter: Alpha(Opacity=30); + border-radius: 8px; +}*/ diff --git a/accounts/static/js/accounts.js b/accounts/static/js/accounts.js new file mode 100644 index 0000000..d885039 --- /dev/null +++ b/accounts/static/js/accounts.js @@ -0,0 +1,29 @@ +/* This script is used to remove user from group, + * user from team or group from team dynamically + * with a ajax request. */ +$('a[role="remove"]').on("click", function () { + var a = $(this); + var href = a.data('href'); + var type = a.data('type'); + a.html('removing...'); + $.ajax(href) + .done(function(data, textStatus) { + a.parents('li').remove(); + var counter = $('#' + type + '-counter'); + var empty = $('#' + type + '-empty'); + var count = parseInt(counter.html()); + count--; + counter.html(count); + if (count < 0) { + // should not happen + window.location.reload(); + } else if (count == 0) { + empty.removeClass('hidden'); + } else { + empty.addClass('hidden'); + } + }) + .fail(function () { + window.location.reload(); + }); +}); diff --git a/issue/static/js/confirm.js b/accounts/static/js/delete_modal.js similarity index 81% rename from issue/static/js/confirm.js rename to accounts/static/js/delete_modal.js index 159d928..791203e 100644 --- a/issue/static/js/confirm.js +++ b/accounts/static/js/delete_modal.js @@ -1,3 +1,5 @@ +/* This script set the action url of the deletion form + * and update messages. */ $('#confirm-delete').on('show.bs.modal', function(e) { var item = $(e.relatedTarget).data('item'); if (!item) { diff --git a/accounts/static/js/team.js b/accounts/static/js/team.js new file mode 100644 index 0000000..8e4f0fc --- /dev/null +++ b/accounts/static/js/team.js @@ -0,0 +1,13 @@ +/* This script switch the visible add form when we + * change between user and group tab on team page. */ +$('a[data-toggle="tab"]').on("show.bs.tab", function () { + var tab = $(this).data('tab'); + var hiddentab; + if (tab == 'user') { + hiddentab = 'group'; + } else { + hiddentab = 'user'; + } + $('#add-' + hiddentab + '-form').addClass('hidden'); + $('#add-' + tab + '-form').removeClass('hidden'); +}); diff --git a/accounts/templatetags/__init__.py b/accounts/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/accounts/templatetags/accounts_tags.py b/accounts/templatetags/accounts_tags.py new file mode 100644 index 0000000..afbd5de --- /dev/null +++ b/accounts/templatetags/accounts_tags.py @@ -0,0 +1,12 @@ +from django import template +from django.core.urlresolvers import reverse +from django.utils.safestring import mark_safe +from django.utils.html import escape + + +register = template.Library() + + +@register.inclusion_tag('accounts/tags/delete_modal.html') +def delete_modal(): + return {} diff --git a/accounts/tests.py b/accounts/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/accounts/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/accounts/urls.py b/accounts/urls.py new file mode 100644 index 0000000..a9ef55e --- /dev/null +++ b/accounts/urls.py @@ -0,0 +1,32 @@ +from django.conf.urls import url, include + + +urlpatterns = [ + # Profile + url(r'^profile$', 'accounts.views.profile', name='profile'), + # Users + url(r'^admin/users/$', 'accounts.views.user_list', name='list-user'), + url(r'^admin/users/add/$', 'accounts.views.user_edit', name='add-user'), + url(r'^admin/users/(?P[0-9]+)/edit/$', 'accounts.views.user_edit', name='edit-user'), + url(r'^admin/users/(?P[0-9]+)/delete/$', 'accounts.views.user_delete', name='delete-user'), + url(r'^admin/users/(?P[0-9]+)/activate/$', 'accounts.views.user_activate', name='activate-user'), + url(r'^admin/users/(?P[0-9]+)/disable/$', 'accounts.views.user_disable', name='disable-user'), + # Groups + url(r'^admin/groups/$', 'accounts.views.group_list', name='list-group'), + url(r'^admin/groups/add/$', 'accounts.views.group_edit', name='add-group'), + url(r'^admin/groups/(?P[0-9]+)/$', 'accounts.views.group_details', name='show-group'), + url(r'^admin/groups/(?P[0-9]+)/edit/$', 'accounts.views.group_edit', name='edit-group'), + url(r'^admin/groups/(?P[0-9]+)/delete/$', 'accounts.views.group_delete', name='delete-group'), + url(r'^admin/groups/(?P[0-9]+)/add-user/$', 'accounts.views.group_add_user', name='add-user-to-group'), + url(r'^admin/groups/(?P[0-9]+)/remove-user/(?P[0-9]+)/$', 'accounts.views.group_remove_user', name='remove-user-from-group'), + # Teams + url(r'^admin/teams/$', 'accounts.views.team_list', name='list-team'), + url(r'^admin/teams/add/$', 'accounts.views.team_edit', name='add-team'), + url(r'^admin/teams/(?P[0-9]+)/$', 'accounts.views.team_details', name='show-team'), + url(r'^admin/teams/(?P[0-9]+)/edit$', 'accounts.views.team_edit', name='edit-team'), + url(r'^admin/teams/(?P[0-9]+)/delete$', 'accounts.views.team_delete', name='delete-team'), + url(r'^admin/teams/(?P[0-9]+)/add-user/$', 'accounts.views.team_add_user', name='add-user-to-team'), + url(r'^admin/teams/(?P[0-9]+)/remove-user/(?P[0-9]+)/$', 'accounts.views.team_remove_user', name='remove-user-from-team'), + url(r'^admin/teams/(?P[0-9]+)/add-group/$', 'accounts.views.team_add_group', name='add-group-to-team'), + url(r'^admin/teams/(?P[0-9]+)/remove-group/(?P[0-9]+)/$', 'accounts.views.team_remove_group', name='remove-group-from-team'), +] diff --git a/accounts/views.py b/accounts/views.py new file mode 100644 index 0000000..d247315 --- /dev/null +++ b/accounts/views.py @@ -0,0 +1,329 @@ +from django.shortcuts import render, redirect, get_object_or_404 +from django.contrib.auth.decorators import login_required +from django.views.decorators.http import require_http_methods +from django.contrib import messages +from django.db.models import Q +from django.core.exceptions import ObjectDoesNotExist + +from django.http import Http404, HttpResponse, JsonResponse + +from permissions.decorators import project_perm_required + +from accounts.models import * +from accounts.forms import * + + +########### +# Profile # +########### + +@login_required +def profile(request): + return render(request, 'accounts/profile.html') + + +######### +# Users # +######### + +@project_perm_required('manage_user') +def user_list(request): + return render(request, 'accounts/user_list.html', { + 'users': User.objects.all(), + }) + + +@project_perm_required('manage_user') +def user_edit(request, user=None): + + if user: + user = get_object_or_404(User, id=user) + + form = UserForm(request.POST or None, instance=user) + if request.method == 'POST' and form.is_valid(): + form.save() + if user: + messages.success(request, 'User modified successfully.') + else: + messages.success(request, 'User added successfully.') + return redirect('list-user') + + return render(request, 'accounts/user_edit.html', { + 'user': user, + 'form': form, + }) + + +@project_perm_required('manage_user') +def user_activate(request, user): + user = get_object_or_404(User, id=user) + if user.is_active: + messages.info(request, 'Account already activated.') + else: + user.is_active = True + user.save() + messages.success(request, 'Account activated successfully.') + return redirect('list-user') + + +@project_perm_required('manage_user') +def user_disable(request, user): + user = get_object_or_404(User, id=user) + if user.is_active: + user.is_active = False + user.save() + messages.success(request, 'Account disabled successfully.') + else: + messages.info(request, 'Account already disabled.') + return redirect('list-user') + + +@require_http_methods(["POST"]) +@project_perm_required('manage_user') +def user_delete(request, user): + user = get_object_or_404(User, id=user) + user.delete() + messages.success(request, 'User deleted successfully.') + return redirect('list-user') + + +########## +# Groups # +########## + +@project_perm_required('manage_group') +def group_list(request): + return render(request, 'accounts/group_list.html', { + 'groups': Group.objects.all(), + }) + + +@project_perm_required('manage_group') +def group_details(request, group): + return render(request, 'accounts/group_details.html', { + 'group': get_object_or_404(Group, id=group), + }) + + +@project_perm_required('manage_group') +def group_edit(request, group=None): + + if group: + group = get_object_or_404(Group, id=group) + + form = GroupForm(request.POST or None, instance=group) + if request.method == 'POST' and form.is_valid(): + formgroup = form.save() + if group: + messages.success(request, 'Group modified successfully.') + else: + messages.success(request, 'Group added successfully.') + return redirect('show-group', formgroup.id) + + return render(request, 'accounts/group_edit.html', { + 'group': group, + 'form': form, + }) + + +@require_http_methods(["POST"]) +@project_perm_required('manage_group') +def group_delete(request, group): + group = get_object_or_404(Group, id=group) + group.delete() + messages.success(request, 'Group deleted successfully.') + return redirect('list-group') + + +@project_perm_required('manage_group') +def group_add_user(request, group): + group = get_object_or_404(Group, id=group) + if request.method == 'POST': + user = request.POST.get('user') + if user: + try: + user = User.objects.get(username=user) + except ObjectDoesNotExist: + messages.error(request, 'User not found.') + else: + if group.users.filter(id=user.id).exists(): + messages.info(request, 'User already in group.') + else: + user.groups.add(group) + user.save() + messages.success(request, 'User added to group successfully.') + else: + messages.error(request, 'User not found.') + return redirect('show-group', group.id) + else: + term = request.GET.get('term') + if not term: + return Http404() + query = Q(username__icontains=term) \ + | Q(first_name__icontains=term) \ + | Q(last_name__icontains=term) + users = User.objects.exclude(groups=group).filter(query)[:10] + response = [] + for user in users: + response += [ { + 'label': user.username_and_fullname, + 'value': user.username, + }] + return JsonResponse(response, safe=False) + + +@project_perm_required('manage_group') +def group_remove_user(request, group, user): + group = get_object_or_404(Group, id=group) + user = get_object_or_404(User, id=user) + user.groups.remove(group) + user.save() + return HttpResponse() + + +######### +# Teams # +######### + +@project_perm_required('manage_team') +def team_list(request): + return render(request, 'accounts/team_list.html', { + 'teams': Team.objects.all(), + }) + + +@project_perm_required('manage_team') +def team_details(request, team): + tab = request.session.pop('team-tab', 'user') + return render(request, 'accounts/team_details.html', { + 'team': get_object_or_404(Team, pk=team), + 'tab': tab, + }) + + +@project_perm_required('manage_team') +def team_edit(request, team=None): + + if team: + team = get_object_or_404(Team, pk=team) + + form = TeamForm(request.POST or None, instance=team) + if request.method == 'POST' and form.is_valid(): + formteam = form.save() + if team: + messages.success(request, 'Team modified successfully.') + else: + messages.success(request, 'Team added successfully.') + return redirect('show-team', formteam.id) + + c = { + 'team': team, + 'form': form, + } + + return render(request, 'accounts/team_edit.html', c) + + +@require_http_methods(["POST"]) +@project_perm_required('manage_team') +def team_delete(request, team): + team = get_object_or_404(Team, pk=team) + team.delete() + messages.success(request, 'Team deleted successfully.') + return redirect('list-team') + + +@project_perm_required('manage_team') +def team_add_user(request, team): + team = get_object_or_404(Team, id=team) + if request.method == 'POST': + user = request.POST.get('user') + if user: + try: + user = User.objects.get(username=user) + except ObjectDoesNotExist: + messages.error(request, 'User not found.') + else: + if team.users.filter(id=user.id).exists(): + messages.info(request, 'User already in team.') + else: + team.users.add(user) + team.save() + messages.success(request, 'User added to team successfully.') + else: + messages.error(request, 'User not found.') + request.session['team-tab'] = 'user' + return redirect('show-team', team.id) + else: + term = request.GET.get('term') + if not term: + return Http404() + query = Q(username__icontains=term) \ + | Q(first_name__icontains=term) \ + | Q(last_name__icontains=term) + users = User.objects \ + .exclude(groups__in=team.groups.all()) \ + .exclude(id__in=team.users.values('id')) \ + .filter(query)[:10] + response = [] + for user in users: + response += [ { + 'label': user.username_and_fullname, + 'value': user.username, + }] + return JsonResponse(response, safe=False) + + +@project_perm_required('manage_team') +def team_remove_user(request, team, user): + team = get_object_or_404(Team, pk=team) + user = get_object_or_404(User, pk=user) + team.users.remove(user) + team.save() + return HttpResponse() + + +@project_perm_required('manage_team') +def team_add_group(request, team): + team = get_object_or_404(Team, id=team) + if request.method == 'POST': + group = request.POST.get('group') + if group: + try: + group = Group.objects.get(name=group) + except ObjectDoesNotExist: + messages.error(request, 'Group not found.') + else: + if team.groups.filter(id=group.id).exists(): + messages.info(request, 'Group already in team.') + else: + team.groups.add(group) + team.save() + messages.success(request, 'Group added to team successfully.') + else: + messages.error(request, 'Group not found.') + request.session['team-tab'] = 'group' + return redirect('show-team', team.id) + else: + term = request.GET.get('term') + if not term: + return Http404() + groups = Group.objects \ + .exclude(id__in=team.groups.values('id')) \ + .filter(name__icontains=term)[:10] + response = [] + for group in groups: + response += [ { + 'label': group.name, + 'value': group.name, + }] + return JsonResponse(response, safe=False) + + +@project_perm_required('manage_team') +def team_remove_group(request, team, group): + team = get_object_or_404(Team, pk=team) + group = get_object_or_404(Group, pk=group) + team.groups.remove(group) + team.save() + return HttpResponse() diff --git a/issue/__init__.py b/issue/__init__.py deleted file mode 100644 index f001650..0000000 --- a/issue/__init__.py +++ /dev/null @@ -1 +0,0 @@ -default_app_config = 'issue.apps.IssueConfig' diff --git a/issue/admin.py b/issue/admin.py deleted file mode 100644 index e155775..0000000 --- a/issue/admin.py +++ /dev/null @@ -1,13 +0,0 @@ -from django.contrib import admin -from issue.models import * - -admin.site.register(User) -admin.site.register(Project) -admin.site.register(Issue) -admin.site.register(Event) -admin.site.register(Label) -admin.site.register(Milestone) -admin.site.register(Settings) -admin.site.register(Team) -admin.site.register(GlobalPermission) -admin.site.register(ProjectPermission) diff --git a/issue/apps.py b/issue/apps.py deleted file mode 100644 index 7035865..0000000 --- a/issue/apps.py +++ /dev/null @@ -1,10 +0,0 @@ -from django.apps import AppConfig - - -class IssueConfig(AppConfig): - - name = 'issue' - verbose_name = "Issue Tracker" - - def ready(self): - import issue.signals diff --git a/issue/fixtures/test_no_project.json b/issue/fixtures/test_no_project.json deleted file mode 100644 index b29de78..0000000 --- a/issue/fixtures/test_no_project.json +++ /dev/null @@ -1 +0,0 @@ -[{"fields": {"model": "logentry", "name": "log entry", "app_label": "admin"}, "model": "contenttypes.contenttype", "pk": 1}, {"fields": {"model": "permission", "name": "permission", "app_label": "auth"}, "model": "contenttypes.contenttype", "pk": 2}, {"fields": {"model": "group", "name": "group", "app_label": "auth"}, "model": "contenttypes.contenttype", "pk": 3}, {"fields": {"model": "user", "name": "user", "app_label": "auth"}, "model": "contenttypes.contenttype", "pk": 4}, {"fields": {"model": "contenttype", "name": "content type", "app_label": "contenttypes"}, "model": "contenttypes.contenttype", "pk": 5}, {"fields": {"model": "session", "name": "session", "app_label": "sessions"}, "model": "contenttypes.contenttype", "pk": 6}, {"fields": {"model": "site", "name": "site", "app_label": "sites"}, "model": "contenttypes.contenttype", "pk": 7}, {"fields": {"model": "project", "name": "project", "app_label": "issue"}, "model": "contenttypes.contenttype", "pk": 8}, {"fields": {"model": "label", "name": "label", "app_label": "issue"}, "model": "contenttypes.contenttype", "pk": 9}, {"fields": {"model": "milestone", "name": "milestone", "app_label": "issue"}, "model": "contenttypes.contenttype", "pk": 10}, {"fields": {"model": "issue", "name": "issue", "app_label": "issue"}, "model": "contenttypes.contenttype", "pk": 11}, {"fields": {"model": "event", "name": "event", "app_label": "issue"}, "model": "contenttypes.contenttype", "pk": 12}, {"fields": {"model": "settings", "name": "settings", "app_label": "issue"}, "model": "contenttypes.contenttype", "pk": 13}, {"fields": {"model": "team", "name": "team", "app_label": "issue"}, "model": "contenttypes.contenttype", "pk": 14}, {"fields": {"model": "globalpermission", "name": "global permission", "app_label": "issue"}, "model": "contenttypes.contenttype", "pk": 15}, {"fields": {"model": "projectpermission", "name": "project permission", "app_label": "issue"}, "model": "contenttypes.contenttype", "pk": 16}, {"fields": {"model": "user", "name": "user", "app_label": "issue"}, "model": "contenttypes.contenttype", "pk": 17}, {"fields": {"expire_date": "2014-08-28T01:49:49.788Z", "session_data": "ZGMxY2QwZDNjOGE4YTQyNDdjNDJkN2E3N2E1NDBiZmJiMWY2ZjE5ODp7Il9hdXRoX3VzZXJfYmFja2VuZCI6ImRqYW5nby5jb250cmliLmF1dGguYmFja2VuZHMuTW9kZWxCYWNrZW5kIiwiX2F1dGhfdXNlcl9oYXNoIjoiNTRjMzVkNzQxZWE1MTRlNWY3M2Y0ZTY3ZTZhNTE3ZDZhNGM1MTgxMyIsIl9hdXRoX3VzZXJfaWQiOjF9"}, "model": "sessions.session", "pk": "w4lxkr65kjw8duym862bbv67we8oyixy"}, {"fields": {"name": "example.com", "domain": "example.com"}, "model": "sites.site", "pk": 1}, {"fields": {"name": "Can add log entry", "codename": "add_logentry", "content_type": 1}, "model": "auth.permission", "pk": 1}, {"fields": {"name": "Can change log entry", "codename": "change_logentry", "content_type": 1}, "model": "auth.permission", "pk": 2}, {"fields": {"name": "Can delete log entry", "codename": "delete_logentry", "content_type": 1}, "model": "auth.permission", "pk": 3}, {"fields": {"name": "Can add permission", "codename": "add_permission", "content_type": 2}, "model": "auth.permission", "pk": 4}, {"fields": {"name": "Can change permission", "codename": "change_permission", "content_type": 2}, "model": "auth.permission", "pk": 5}, {"fields": {"name": "Can delete permission", "codename": "delete_permission", "content_type": 2}, "model": "auth.permission", "pk": 6}, {"fields": {"name": "Can add group", "codename": "add_group", "content_type": 3}, "model": "auth.permission", "pk": 7}, {"fields": {"name": "Can change group", "codename": "change_group", "content_type": 3}, "model": "auth.permission", "pk": 8}, {"fields": {"name": "Can delete group", "codename": "delete_group", "content_type": 3}, "model": "auth.permission", "pk": 9}, {"fields": {"name": "Can add user", "codename": "add_user", "content_type": 4}, "model": "auth.permission", "pk": 10}, {"fields": {"name": "Can change user", "codename": "change_user", "content_type": 4}, "model": "auth.permission", "pk": 11}, {"fields": {"name": "Can delete user", "codename": "delete_user", "content_type": 4}, "model": "auth.permission", "pk": 12}, {"fields": {"name": "Can add content type", "codename": "add_contenttype", "content_type": 5}, "model": "auth.permission", "pk": 13}, {"fields": {"name": "Can change content type", "codename": "change_contenttype", "content_type": 5}, "model": "auth.permission", "pk": 14}, {"fields": {"name": "Can delete content type", "codename": "delete_contenttype", "content_type": 5}, "model": "auth.permission", "pk": 15}, {"fields": {"name": "Can add session", "codename": "add_session", "content_type": 6}, "model": "auth.permission", "pk": 16}, {"fields": {"name": "Can change session", "codename": "change_session", "content_type": 6}, "model": "auth.permission", "pk": 17}, {"fields": {"name": "Can delete session", "codename": "delete_session", "content_type": 6}, "model": "auth.permission", "pk": 18}, {"fields": {"name": "Can add site", "codename": "add_site", "content_type": 7}, "model": "auth.permission", "pk": 19}, {"fields": {"name": "Can change site", "codename": "change_site", "content_type": 7}, "model": "auth.permission", "pk": 20}, {"fields": {"name": "Can delete site", "codename": "delete_site", "content_type": 7}, "model": "auth.permission", "pk": 21}, {"fields": {"name": "Can add project", "codename": "add_project", "content_type": 8}, "model": "auth.permission", "pk": 22}, {"fields": {"name": "Can change project", "codename": "change_project", "content_type": 8}, "model": "auth.permission", "pk": 23}, {"fields": {"name": "Can delete project", "codename": "delete_project", "content_type": 8}, "model": "auth.permission", "pk": 24}, {"fields": {"name": "Can add label", "codename": "add_label", "content_type": 9}, "model": "auth.permission", "pk": 25}, {"fields": {"name": "Can change label", "codename": "change_label", "content_type": 9}, "model": "auth.permission", "pk": 26}, {"fields": {"name": "Can delete label", "codename": "delete_label", "content_type": 9}, "model": "auth.permission", "pk": 27}, {"fields": {"name": "Can add milestone", "codename": "add_milestone", "content_type": 10}, "model": "auth.permission", "pk": 28}, {"fields": {"name": "Can change milestone", "codename": "change_milestone", "content_type": 10}, "model": "auth.permission", "pk": 29}, {"fields": {"name": "Can delete milestone", "codename": "delete_milestone", "content_type": 10}, "model": "auth.permission", "pk": 30}, {"fields": {"name": "Can add issue", "codename": "add_issue", "content_type": 11}, "model": "auth.permission", "pk": 31}, {"fields": {"name": "Can change issue", "codename": "change_issue", "content_type": 11}, "model": "auth.permission", "pk": 32}, {"fields": {"name": "Can delete issue", "codename": "delete_issue", "content_type": 11}, "model": "auth.permission", "pk": 33}, {"fields": {"name": "Can add event", "codename": "add_event", "content_type": 12}, "model": "auth.permission", "pk": 34}, {"fields": {"name": "Can change event", "codename": "change_event", "content_type": 12}, "model": "auth.permission", "pk": 35}, {"fields": {"name": "Can delete event", "codename": "delete_event", "content_type": 12}, "model": "auth.permission", "pk": 36}, {"fields": {"name": "Can add settings", "codename": "add_settings", "content_type": 13}, "model": "auth.permission", "pk": 37}, {"fields": {"name": "Can change settings", "codename": "change_settings", "content_type": 13}, "model": "auth.permission", "pk": 38}, {"fields": {"name": "Can delete settings", "codename": "delete_settings", "content_type": 13}, "model": "auth.permission", "pk": 39}, {"fields": {"name": "Can add team", "codename": "add_team", "content_type": 14}, "model": "auth.permission", "pk": 40}, {"fields": {"name": "Can change team", "codename": "change_team", "content_type": 14}, "model": "auth.permission", "pk": 41}, {"fields": {"name": "Can delete team", "codename": "delete_team", "content_type": 14}, "model": "auth.permission", "pk": 42}, {"fields": {"name": "Can add global permission", "codename": "add_globalpermission", "content_type": 15}, "model": "auth.permission", "pk": 43}, {"fields": {"name": "Can change global permission", "codename": "change_globalpermission", "content_type": 15}, "model": "auth.permission", "pk": 44}, {"fields": {"name": "Can delete global permission", "codename": "delete_globalpermission", "content_type": 15}, "model": "auth.permission", "pk": 45}, {"fields": {"name": "Can add project permission", "codename": "add_projectpermission", "content_type": 16}, "model": "auth.permission", "pk": 46}, {"fields": {"name": "Can change project permission", "codename": "change_projectpermission", "content_type": 16}, "model": "auth.permission", "pk": 47}, {"fields": {"name": "Can delete project permission", "codename": "delete_projectpermission", "content_type": 16}, "model": "auth.permission", "pk": 48}, {"fields": {"username": "user2", "last_name": "", "date_joined": "2014-08-14T01:49:37.915Z", "last_login": "2014-08-14T01:49:49.666Z", "is_superuser": true, "user_permissions": [], "is_staff": true, "password": "pbkdf2_sha256$12000$rMF2B9B9xUr0$+jQBIlE3u1PVXSd7tX+GkI0EsHm6vpPcjBUxRcgEPpA=", "groups": [], "first_name": "", "email": "", "is_active": true}, "model": "issue.user", "pk": 1}, {"fields": {"username": "user1", "last_name": "", "date_joined": "2014-08-14T01:50:01.882Z", "last_login": "2014-08-14T01:50:01.881Z", "is_superuser": false, "user_permissions": [], "is_staff": false, "password": "pbkdf2_sha256$12000$xLQyPps0dBdw$3wCLUKicq5d3c4iru0Nl0yU+8ZzsirJ2h+5eYsRWOIw=", "groups": [], "first_name": "", "email": "", "is_active": true}, "model": "issue.user", "pk": 2}, {"fields": {"action_flag": 1, "object_repr": "user1", "action_time": "2014-08-14T01:50:01.938Z", "object_id": "2", "change_message": "", "user": 1, "content_type": 4}, "model": "admin.logentry", "pk": 1}] diff --git a/issue/fixtures/test_perms.json b/issue/fixtures/test_perms.json deleted file mode 100644 index ace3bc6..0000000 --- a/issue/fixtures/test_perms.json +++ /dev/null @@ -1 +0,0 @@ -[{"pk": 1, "fields": {"project": "project-1", "name": "bug", "deleted": false, "color": "#FF0000", "inverted": true}, "model": "issue.label"}, {"pk": 2, "fields": {"project": "project-1", "name": "feature", "deleted": false, "color": "#00A000", "inverted": true}, "model": "issue.label"}, {"pk": 3, "fields": {"project": "project-1", "name": "documentation", "deleted": false, "color": "#1D3DBE", "inverted": true}, "model": "issue.label"}, {"pk": 4, "fields": {"project": "project-2", "name": "bug", "deleted": false, "color": "#FF0000", "inverted": true}, "model": "issue.label"}, {"pk": 5, "fields": {"project": "project-2", "name": "feature", "deleted": false, "color": "#00A000", "inverted": true}, "model": "issue.label"}, {"pk": 6, "fields": {"project": "project-2", "name": "documentation", "deleted": false, "color": "#1D3DBE", "inverted": true}, "model": "issue.label"}, {"pk": 7, "fields": {"project": "project-2", "name": "low priority", "deleted": false, "color": "#ffffff", "inverted": false}, "model": "issue.label"}, {"pk": 8, "fields": {"project": "project-3", "name": "bug", "deleted": false, "color": "#FF0000", "inverted": true}, "model": "issue.label"}, {"pk": 9, "fields": {"project": "project-3", "name": "feature", "deleted": false, "color": "#00A000", "inverted": true}, "model": "issue.label"}, {"pk": 10, "fields": {"project": "project-3", "name": "documentation", "deleted": false, "color": "#1D3DBE", "inverted": true}, "model": "issue.label"}, {"pk": 1, "fields": {"project": "project-1", "name": "v1.0", "closed": false, "due_date": "2014-08-12T19:43:00Z"}, "model": "issue.milestone"}, {"pk": 2, "fields": {"project": "project-2", "name": "v1.0", "closed": false, "due_date": null}, "model": "issue.milestone"}, {"pk": 3, "fields": {"project": "project-2", "name": "v2.0", "closed": false, "due_date": "2014-08-12T22:18:00Z"}, "model": "issue.milestone"}, {"pk": 1, "fields": {"manage_global_permission": false, "manage_tags": false, "manage_team": false, "grantee_name": "user3", "modify_project": false, "create_issue": false, "add_team": true, "manage_issue": false, "grantee_type": 0, "create_comment": false, "modify_comment": false, "manage_project_permission": false, "delete_tags": false, "modify_issue": false, "delete_project": false, "create_project": true, "delete_issue": false, "delete_comment": false}, "model": "issue.globalpermission"}, {"pk": 4, "fields": {"manage_global_permission": false, "manage_tags": false, "manage_team": false, "grantee_name": "group1", "modify_project": true, "create_issue": false, "add_team": true, "manage_issue": false, "grantee_type": 1, "create_comment": false, "modify_comment": false, "manage_project_permission": false, "delete_tags": false, "modify_issue": false, "delete_project": false, "create_project": false, "delete_issue": false, "delete_comment": false}, "model": "issue.globalpermission"}, {"pk": 5, "fields": {"manage_global_permission": false, "manage_tags": false, "manage_team": false, "grantee_name": "team1", "modify_project": false, "create_issue": false, "add_team": true, "manage_issue": false, "grantee_type": 2, "create_comment": false, "modify_comment": false, "manage_project_permission": false, "delete_tags": false, "modify_issue": false, "delete_project": true, "create_project": false, "delete_issue": false, "delete_comment": false}, "model": "issue.globalpermission"}, {"pk": 6, "fields": {"manage_global_permission": true, "manage_tags": false, "manage_team": false, "grantee_name": "user15", "modify_project": false, "create_issue": false, "add_team": false, "manage_issue": false, "grantee_type": 0, "create_comment": false, "modify_comment": false, "manage_project_permission": false, "delete_tags": false, "modify_issue": false, "delete_project": false, "create_project": false, "delete_issue": false, "delete_comment": false}, "model": "issue.globalpermission"}, {"pk": 1, "fields": {"project": "project-1", "manage_tags": false, "grantee_name": "user3", "create_issue": true, "manage_issue": false, "grantee_type": 0, "modify_comment": false, "manage_project_permission": false, "delete_tags": false, "modify_issue": false, "create_comment": false, "delete_issue": false, "delete_comment": false}, "model": "issue.projectpermission"}, {"pk": 2, "fields": {"project": "project-1", "manage_tags": false, "grantee_name": "group1", "create_issue": false, "manage_issue": false, "grantee_type": 1, "modify_comment": false, "manage_project_permission": false, "delete_tags": false, "modify_issue": true, "create_comment": false, "delete_issue": false, "delete_comment": false}, "model": "issue.projectpermission"}, {"pk": 3, "fields": {"project": "project-1", "manage_tags": false, "grantee_name": "team1", "create_issue": false, "manage_issue": false, "grantee_type": 2, "modify_comment": false, "manage_project_permission": false, "delete_tags": false, "modify_issue": false, "create_comment": false, "delete_issue": true, "delete_comment": false}, "model": "issue.projectpermission"}, {"pk": 4, "fields": {"project": "project-2", "manage_tags": false, "grantee_name": "user2", "create_issue": false, "manage_issue": false, "grantee_type": 0, "modify_comment": false, "manage_project_permission": false, "delete_tags": false, "modify_issue": false, "create_comment": false, "delete_issue": false, "delete_comment": false}, "model": "issue.projectpermission"}, {"pk": 5, "fields": {"project": "project-2", "manage_tags": false, "grantee_name": "user5", "create_issue": true, "manage_issue": false, "grantee_type": 0, "modify_comment": false, "manage_project_permission": false, "delete_tags": false, "modify_issue": false, "create_comment": false, "delete_issue": false, "delete_comment": false}, "model": "issue.projectpermission"}, {"pk": 6, "fields": {"project": "project-2", "manage_tags": false, "grantee_name": "user6", "create_issue": false, "manage_issue": true, "grantee_type": 0, "modify_comment": false, "manage_project_permission": false, "delete_tags": false, "modify_issue": false, "create_comment": false, "delete_issue": false, "delete_comment": false}, "model": "issue.projectpermission"}, {"pk": 7, "fields": {"project": "project-2", "manage_tags": false, "grantee_name": "user7", "create_issue": false, "manage_issue": false, "grantee_type": 0, "modify_comment": false, "manage_project_permission": false, "delete_tags": false, "modify_issue": true, "create_comment": false, "delete_issue": false, "delete_comment": false}, "model": "issue.projectpermission"}, {"pk": 8, "fields": {"project": "project-2", "manage_tags": false, "grantee_name": "user8", "create_issue": false, "manage_issue": false, "grantee_type": 0, "modify_comment": false, "manage_project_permission": false, "delete_tags": false, "modify_issue": false, "create_comment": false, "delete_issue": true, "delete_comment": false}, "model": "issue.projectpermission"}, {"pk": 9, "fields": {"project": "project-2", "manage_tags": false, "grantee_name": "user9", "create_issue": false, "manage_issue": false, "grantee_type": 0, "modify_comment": false, "manage_project_permission": false, "delete_tags": false, "modify_issue": false, "create_comment": true, "delete_issue": false, "delete_comment": false}, "model": "issue.projectpermission"}, {"pk": 10, "fields": {"project": "project-2", "manage_tags": false, "grantee_name": "user10", "create_issue": false, "manage_issue": false, "grantee_type": 0, "modify_comment": true, "manage_project_permission": false, "delete_tags": false, "modify_issue": false, "create_comment": false, "delete_issue": false, "delete_comment": false}, "model": "issue.projectpermission"}, {"pk": 11, "fields": {"project": "project-2", "manage_tags": false, "grantee_name": "user11", "create_issue": false, "manage_issue": false, "grantee_type": 0, "modify_comment": false, "manage_project_permission": false, "delete_tags": false, "modify_issue": false, "create_comment": false, "delete_issue": false, "delete_comment": true}, "model": "issue.projectpermission"}, {"pk": 12, "fields": {"project": "project-2", "manage_tags": true, "grantee_name": "user12", "create_issue": false, "manage_issue": false, "grantee_type": 0, "modify_comment": false, "manage_project_permission": false, "delete_tags": false, "modify_issue": false, "create_comment": false, "delete_issue": false, "delete_comment": false}, "model": "issue.projectpermission"}, {"pk": 13, "fields": {"project": "project-2", "manage_tags": false, "grantee_name": "user13", "create_issue": false, "manage_issue": false, "grantee_type": 0, "modify_comment": false, "manage_project_permission": false, "delete_tags": true, "modify_issue": false, "create_comment": false, "delete_issue": false, "delete_comment": false}, "model": "issue.projectpermission"}, {"pk": 14, "fields": {"project": "project-2", "manage_tags": false, "grantee_name": "user14", "create_issue": false, "manage_issue": false, "grantee_type": 0, "modify_comment": false, "manage_project_permission": true, "delete_tags": false, "modify_issue": false, "create_comment": false, "delete_issue": false, "delete_comment": false}, "model": "issue.projectpermission"}, {"pk": 1, "fields": {"name": "group1", "permissions": []}, "model": "auth.group"}, {"pk": 1, "fields": {"date_joined": "2014-08-08T16:41:05.033Z", "password": "pbkdf2_sha256$12000$qy5asFJAVx57$RneUin16DMOU6PIIdctZqjtUa0PjE2yDIiGAXg3qteI=", "first_name": "", "username": "admin", "is_superuser": true, "email": "", "is_staff": true, "user_permissions": [], "groups": [], "is_active": true, "last_name": "", "last_login": "2014-08-23T05:55:52.315Z"}, "model": "issue.user"}, {"pk": 2, "fields": {"date_joined": "2014-08-08T16:42:31Z", "password": "pbkdf2_sha256$12000$drENNTEqIkBE$2GqTBGncULxAw9bLc6vyta1p/uJGFYuU0zrurDK77nI=", "first_name": "", "username": "user1", "is_superuser": false, "email": "", "is_staff": false, "user_permissions": [], "groups": [], "is_active": true, "last_name": "", "last_login": "2014-08-23T06:07:06.755Z"}, "model": "issue.user"}, {"pk": 3, "fields": {"date_joined": "2014-08-08T18:26:40Z", "password": "pbkdf2_sha256$12000$3EduBVRie3iD$qHAbiBebSQq7RlJogSxeoWlinw8w7bcSXdOgn3WiyeM=", "first_name": "", "username": "user2", "is_superuser": false, "email": "", "is_staff": false, "user_permissions": [], "groups": [1], "is_active": true, "last_name": "", "last_login": "2014-08-23T06:08:36.529Z"}, "model": "issue.user"}, {"pk": 4, "fields": {"date_joined": "2014-08-08T18:27:04Z", "password": "pbkdf2_sha256$12000$9ReiA9Yfsiu2$o+T1rHBTb1wFYIbzDoGliOFhqdHMvvLyOAk80JQnCeg=", "first_name": "", "username": "user3", "is_superuser": false, "email": "", "is_staff": false, "user_permissions": [], "groups": [], "is_active": true, "last_name": "", "last_login": "2014-08-23T06:02:40.746Z"}, "model": "issue.user"}, {"pk": 5, "fields": {"date_joined": "2014-08-08T18:54:24.705Z", "password": "pbkdf2_sha256$12000$V83PwKTT9uhM$E+uXxLKQ7nmrDnJH+b/+MSp8jfkMkH0UxOZjqSjHJw4=", "first_name": "", "username": "user4", "is_superuser": false, "email": "", "is_staff": false, "user_permissions": [], "groups": [], "is_active": true, "last_name": "", "last_login": "2014-08-08T18:54:24.705Z"}, "model": "issue.user"}, {"pk": 6, "fields": {"date_joined": "2014-08-13T00:06:33.263Z", "password": "pbkdf2_sha256$12000$NOPq906lrJ8H$BMHx3slAgeQ3wgCHHjfULRgdhjLoDpQHzzlpzGvK2r0=", "first_name": "", "username": "user5", "is_superuser": false, "email": "", "is_staff": false, "user_permissions": [], "groups": [], "is_active": true, "last_name": "", "last_login": "2014-08-13T06:07:48.508Z"}, "model": "issue.user"}, {"pk": 7, "fields": {"date_joined": "2014-08-13T00:06:45.006Z", "password": "pbkdf2_sha256$12000$qIg3RVaNemsx$aKfUwN4s5szqS2c6t4s3xUm13F9am+rrYdeS17fNA+A=", "first_name": "", "username": "user6", "is_superuser": false, "email": "", "is_staff": false, "user_permissions": [], "groups": [], "is_active": true, "last_name": "", "last_login": "2014-08-13T00:06:45.006Z"}, "model": "issue.user"}, {"pk": 8, "fields": {"date_joined": "2014-08-13T00:06:51.123Z", "password": "pbkdf2_sha256$12000$aMEK1COCL2AQ$V1oN9jBNEk74+VY5Bzk+wgt+iaaDkt5F4lEfoZFuVEM=", "first_name": "", "username": "user7", "is_superuser": false, "email": "", "is_staff": false, "user_permissions": [], "groups": [], "is_active": true, "last_name": "", "last_login": "2014-08-13T00:06:51.123Z"}, "model": "issue.user"}, {"pk": 9, "fields": {"date_joined": "2014-08-13T00:06:58.258Z", "password": "pbkdf2_sha256$12000$pm8DYEcJ3tCd$dCOqr1XBut+SeZPcyaB/n/YvSl82x/ZTqQlqvYoTRAk=", "first_name": "", "username": "user8", "is_superuser": false, "email": "", "is_staff": false, "user_permissions": [], "groups": [], "is_active": true, "last_name": "", "last_login": "2014-08-13T00:06:58.258Z"}, "model": "issue.user"}, {"pk": 10, "fields": {"date_joined": "2014-08-13T00:07:06.058Z", "password": "pbkdf2_sha256$12000$rz8pHZi9D2wd$nS0Wzv6wKGIdaHf/w2W3s4WPc1UzNvrLGpxl73pDlc8=", "first_name": "", "username": "user9", "is_superuser": false, "email": "", "is_staff": false, "user_permissions": [], "groups": [], "is_active": true, "last_name": "", "last_login": "2014-08-13T00:07:06.058Z"}, "model": "issue.user"}, {"pk": 11, "fields": {"date_joined": "2014-08-19T02:20:10.928Z", "password": "pbkdf2_sha256$12000$PnfXEvOFXdkH$KNeQ9EmxcWQItwn0O6GAgrGqSsADpkLHDptLOZqRUz8=", "first_name": "", "username": "user10", "is_superuser": false, "email": "", "is_staff": false, "user_permissions": [], "groups": [], "is_active": true, "last_name": "", "last_login": "2014-08-19T02:20:10.928Z"}, "model": "issue.user"}, {"pk": 12, "fields": {"date_joined": "2014-08-19T02:20:24.679Z", "password": "pbkdf2_sha256$12000$YoD2cw2DWBrJ$grA3gBwYHXTFBm7rYqaYLbjTWfoxg3dMAXAJ5otVUeE=", "first_name": "", "username": "user11", "is_superuser": false, "email": "", "is_staff": false, "user_permissions": [], "groups": [], "is_active": true, "last_name": "", "last_login": "2014-08-19T02:20:24.679Z"}, "model": "issue.user"}, {"pk": 13, "fields": {"date_joined": "2014-08-19T02:20:31.288Z", "password": "pbkdf2_sha256$12000$PtLTOXgu60Fv$fKxvZLB7n70jbzDr5B9IJPcqz+KYdo2kjEsbe4mcpB8=", "first_name": "", "username": "user12", "is_superuser": false, "email": "", "is_staff": false, "user_permissions": [], "groups": [], "is_active": true, "last_name": "", "last_login": "2014-08-19T02:20:31.288Z"}, "model": "issue.user"}, {"pk": 14, "fields": {"date_joined": "2014-08-19T02:20:40.487Z", "password": "pbkdf2_sha256$12000$iOqaasAgA88c$A2lgrA4arjKQiRZ8X/N4X0TwCjk6Pq3b6MmVvFXJivY=", "first_name": "", "username": "user13", "is_superuser": false, "email": "", "is_staff": false, "user_permissions": [], "groups": [], "is_active": true, "last_name": "", "last_login": "2014-08-19T02:20:40.487Z"}, "model": "issue.user"}, {"pk": 15, "fields": {"date_joined": "2014-08-19T02:20:47.060Z", "password": "pbkdf2_sha256$12000$GrNF0rTyUow1$JtdT6tw1KxXEf7sgkBa3EAzWC8+uh1y1wybeZVIT6vc=", "first_name": "", "username": "user14", "is_superuser": false, "email": "", "is_staff": false, "user_permissions": [], "groups": [], "is_active": true, "last_name": "", "last_login": "2014-08-19T02:20:47.059Z"}, "model": "issue.user"}, {"pk": 16, "fields": {"date_joined": "2014-08-19T02:20:55.218Z", "password": "pbkdf2_sha256$12000$ixz6C18KVI4S$wnNJMJwB4oO0yz4NUTqbtrAdq6LxRbXFrq3mq0NkA3w=", "first_name": "", "username": "user15", "is_superuser": false, "email": "", "is_staff": false, "user_permissions": [], "groups": [], "is_active": true, "last_name": "", "last_login": "2014-08-19T02:20:55.218Z"}, "model": "issue.user"}, {"pk": 17, "fields": {"date_joined": "2014-08-19T02:21:03.081Z", "password": "pbkdf2_sha256$12000$LBpJTHuCS7Ri$G8rFIAcTxCUY7gYYoZ1v+qaHhNkitAibIkijZvSJnkU=", "first_name": "", "username": "user16", "is_superuser": false, "email": "", "is_staff": false, "user_permissions": [], "groups": [], "is_active": true, "last_name": "", "last_login": "2014-08-19T02:21:03.081Z"}, "model": "issue.user"}, {"pk": "project-1", "fields": {"access": 1, "description": "This is a public project.", "subscribers": [], "display_name": "Project 1"}, "model": "issue.project"}, {"pk": "project-2", "fields": {"access": 3, "description": "This is a private project.", "subscribers": [], "display_name": "Project 2"}, "model": "issue.project"}, {"pk": "project-3", "fields": {"access": 2, "description": "This is a project reachable only by connected users.", "subscribers": [1], "display_name": "Project 3"}, "model": "issue.project"}, {"pk": 1, "fields": {"project": "project-1", "author": 1, "milestone": 1, "assignee": null, "labels": [1], "title": "Issue 1", "opened_at": "2014-08-13T02:42:36.703Z", "subscribers": [], "id": 1, "closed": false}, "model": "issue.issue"}, {"pk": 2, "fields": {"project": "project-2", "author": 1, "milestone": 3, "assignee": null, "labels": [5, 7], "title": "Issue 1", "opened_at": "2014-08-13T05:18:20.391Z", "subscribers": [], "id": 1, "closed": false}, "model": "issue.issue"}, {"pk": 3, "fields": {"project": "project-2", "author": 1, "milestone": null, "assignee": null, "labels": [], "title": "Issue 2", "opened_at": "2014-08-13T06:04:58.153Z", "subscribers": [], "id": 2, "closed": true}, "model": "issue.issue"}, {"pk": 1, "fields": {"author": 1, "code": 11, "date": "2014-08-13T02:42:36.879Z", "_args": "{}", "issue": 1, "additionnal_section": "This is the first issue."}, "model": "issue.event"}, {"pk": 2, "fields": {"author": 1, "code": 4, "date": "2014-08-13T02:43:24.991Z", "_args": "{\"label\": 1}", "issue": 1, "additionnal_section": ""}, "model": "issue.event"}, {"pk": 3, "fields": {"author": 1, "code": 6, "date": "2014-08-13T02:43:36.623Z", "_args": "{\"milestone\": \"v1.0\"}", "issue": 1, "additionnal_section": ""}, "model": "issue.event"}, {"pk": 4, "fields": {"author": 1, "code": 11, "date": "2014-08-13T05:18:20.585Z", "_args": "{}", "issue": 2, "additionnal_section": "This is the first issue."}, "model": "issue.event"}, {"pk": 5, "fields": {"author": 1, "code": 4, "date": "2014-08-13T05:18:25.994Z", "_args": "{\"label\": 4}", "issue": 2, "additionnal_section": ""}, "model": "issue.event"}, {"pk": 6, "fields": {"author": 1, "code": 4, "date": "2014-08-13T05:18:30.765Z", "_args": "{\"label\": 5}", "issue": 2, "additionnal_section": ""}, "model": "issue.event"}, {"pk": 7, "fields": {"author": 1, "code": 5, "date": "2014-08-13T05:18:33.342Z", "_args": "{\"label\": 4}", "issue": 2, "additionnal_section": ""}, "model": "issue.event"}, {"pk": 8, "fields": {"author": 1, "code": 6, "date": "2014-08-13T05:18:40.484Z", "_args": "{\"milestone\": \"v1.0\"}", "issue": 2, "additionnal_section": ""}, "model": "issue.event"}, {"pk": 9, "fields": {"author": 1, "code": 7, "date": "2014-08-13T05:18:49.552Z", "_args": "{\"new_milestone\": \"v2.0\", \"old_milestone\": \"v1.0\"}", "issue": 2, "additionnal_section": ""}, "model": "issue.event"}, {"pk": 10, "fields": {"author": 1, "code": 10, "date": "2014-08-13T05:19:04.929Z", "_args": "{}", "issue": 2, "additionnal_section": "Done"}, "model": "issue.event"}, {"pk": 11, "fields": {"author": 1, "code": 1, "date": "2014-08-13T05:19:07.194Z", "_args": "{}", "issue": 2, "additionnal_section": ""}, "model": "issue.event"}, {"pk": 12, "fields": {"author": 1, "code": 10, "date": "2014-08-13T05:19:20.077Z", "_args": "{}", "issue": 2, "additionnal_section": "Missing things"}, "model": "issue.event"}, {"pk": 13, "fields": {"author": 1, "code": 2, "date": "2014-08-13T05:19:22.049Z", "_args": "{}", "issue": 2, "additionnal_section": ""}, "model": "issue.event"}, {"pk": 14, "fields": {"author": 1, "code": 4, "date": "2014-08-13T05:23:17.622Z", "_args": "{\"label\": 7}", "issue": 2, "additionnal_section": ""}, "model": "issue.event"}, {"pk": 15, "fields": {"author": 1, "code": 11, "date": "2014-08-13T06:04:58.351Z", "_args": "{}", "issue": 3, "additionnal_section": "This is the second issue."}, "model": "issue.event"}, {"pk": 16, "fields": {"author": 1, "code": 1, "date": "2014-08-13T06:05:00.212Z", "_args": "{}", "issue": 3, "additionnal_section": ""}, "model": "issue.event"}, {"pk": 1, "fields": {"name": "team1", "groups": [], "users": [2]}, "model": "issue.team"}, {"pk": 2, "fields": {"name": "team2", "groups": [1], "users": []}, "model": "issue.team"}] \ No newline at end of file diff --git a/issue/forms.py b/issue/forms.py deleted file mode 100644 index 31578a5..0000000 --- a/issue/forms.py +++ /dev/null @@ -1,80 +0,0 @@ -from django import forms -from django.forms.models import modelform_factory - -from bootstrap3_datetime.widgets import DateTimePicker -from django_markdown.widgets import MarkdownWidget - -from issue.models import * - - -AddProjectForm = modelform_factory(Project, - fields=['display_name', 'name', 'description', 'access']) -EditProjectForm = modelform_factory(Project, - fields=['display_name', 'description', 'access']) -LabelForm = modelform_factory(Label, - fields=['name', 'color', 'inverted']) -TeamForm = modelform_factory(Team, - fields=['name', 'users', 'groups']) - - -class MilestoneForm(forms.ModelForm): - - class Meta: - model = Milestone - fields = ['name', 'due_date'] - widgets = { - 'due_date': DateTimePicker(format="%Y-%m-%d %H:%M"), - } - - -class IssueForm(forms.Form): - title = forms.CharField(max_length=128) - description = forms.CharField(widget=MarkdownWidget, required=False) - - -class CommentForm(forms.Form): - comment = forms.CharField(widget=MarkdownWidget) - - -class PermissionForm(forms.ModelForm): - - class Meta: - model = PermissionModel - exclude = [] - abstract = True - - def clean(self): - - data = super(PermissionForm, self).clean() - - if 'grantee_name' not in data or 'grantee_type' not in data: - # a field required error will be printed so we dont care - return data - - name = data['grantee_name'] - - if int(data['grantee_type']) == PermissionModel.GRANTEE_USER: - if not User.objects.filter(username=name).exists(): - raise ValidationError("User '%s' does not exists." % name) - elif int(data['grantee_type']) == PermissionModel.GRANTEE_GROUP: - if not Group.objects.filter(name=name).exists(): - raise ValidationError("Group '%s' does not exists." % name) - elif int(data['grantee_type']) == PermissionModel.GRANTEE_TEAM: - if not Team.objects.filter(name=name).exists(): - raise ValidationError("Team '%s' does not exists." % name) - - return data - - -class GlobalPermissionForm(PermissionForm): - - class Meta: - model = GlobalPermission - exclude = [] - - -class ProjectPermissionForm(PermissionForm): - - class Meta: - model = ProjectPermission - exclude = [] diff --git a/issue/migrations/0001_initial.py b/issue/migrations/0001_initial.py deleted file mode 100644 index e0c41ff..0000000 --- a/issue/migrations/0001_initial.py +++ /dev/null @@ -1,230 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import models, migrations -import colorful.fields -from django.conf import settings -import django.utils.timezone -import django.core.validators - - -class Migration(migrations.Migration): - - dependencies = [ - ('auth', '0001_initial'), - ('sites', '0001_initial'), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.CreateModel( - name='User', - fields=[ - ('id', models.AutoField(primary_key=True, verbose_name='ID', auto_created=True, serialize=False)), - ('password', models.CharField(verbose_name='password', max_length=128)), - ('last_login', models.DateTimeField(verbose_name='last login', default=django.utils.timezone.now)), - ('is_superuser', models.BooleanField(verbose_name='superuser status', default=False, help_text='Designates that this user has all permissions without explicitly assigning them.')), - ('username', models.CharField(unique=True, verbose_name='username', validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username.', 'invalid')], help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=30)), - ('first_name', models.CharField(blank=True, verbose_name='first name', max_length=30)), - ('last_name', models.CharField(blank=True, verbose_name='last name', max_length=30)), - ('email', models.EmailField(blank=True, verbose_name='email address', max_length=75)), - ('is_staff', models.BooleanField(verbose_name='staff status', default=False, help_text='Designates whether the user can log into this admin site.')), - ('is_active', models.BooleanField(verbose_name='active', default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.')), - ('date_joined', models.DateTimeField(verbose_name='date joined', default=django.utils.timezone.now)), - ('groups', models.ManyToManyField(verbose_name='groups', to='auth.Group', blank=True)), - ('user_permissions', models.ManyToManyField(verbose_name='user permissions', to='auth.Permission', blank=True)), - ], - options={ - 'verbose_name': 'user', - 'abstract': False, - 'verbose_name_plural': 'users', - }, - bases=(models.Model,), - ), - migrations.CreateModel( - name='Event', - fields=[ - ('id', models.AutoField(primary_key=True, verbose_name='ID', auto_created=True, serialize=False)), - ('date', models.DateTimeField(auto_now_add=True)), - ('code', models.IntegerField(default=0)), - ('_args', models.CharField(max_length=1024, default='{}', blank=True)), - ('additionnal_section', models.TextField(blank=True, default='')), - ('author', models.ForeignKey(to=settings.AUTH_USER_MODEL)), - ], - options={ - }, - bases=(models.Model,), - ), - migrations.CreateModel( - name='GlobalPermission', - fields=[ - ('id', models.AutoField(primary_key=True, verbose_name='ID', auto_created=True, serialize=False)), - ('grantee_type', models.IntegerField(choices=[(0, 'User'), (1, 'Group'), (2, 'Team')], verbose_name='Type', default=0)), - ('grantee_name', models.CharField(verbose_name='Name', max_length=50)), - ('create_project', models.BooleanField(default=True)), - ('modify_project', models.BooleanField(default=False)), - ('delete_project', models.BooleanField(default=False)), - ('add_team', models.BooleanField(default=True)), - ('manage_team', models.BooleanField(default=False)), - ('manage_global_permission', models.BooleanField(default=False)), - ('manage_project_permission', models.BooleanField(default=False)), - ('create_issue', models.BooleanField(default=True)), - ('modify_issue', models.BooleanField(default=False)), - ('manage_issue', models.BooleanField(default=False)), - ('delete_issue', models.BooleanField(default=False)), - ('create_comment', models.BooleanField(default=True)), - ('modify_comment', models.BooleanField(default=False)), - ('delete_comment', models.BooleanField(default=False)), - ('manage_tags', models.BooleanField(default=False)), - ('delete_tags', models.BooleanField(default=False)), - ], - options={ - 'abstract': False, - }, - bases=(models.Model,), - ), - migrations.CreateModel( - name='Issue', - fields=[ - ('global_id', models.AutoField(primary_key=True, serialize=False)), - ('id', models.IntegerField(editable=False)), - ('title', models.CharField(max_length=128)), - ('opened_at', models.DateTimeField(auto_now_add=True)), - ('closed', models.BooleanField(default=False)), - ('assignee', models.ForeignKey(blank=True, to=settings.AUTH_USER_MODEL, null=True)), - ('author', models.ForeignKey(to=settings.AUTH_USER_MODEL)), - ('subscribers', models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, null=True)), - ], - options={ - }, - bases=(models.Model,), - ), - migrations.AddField( - model_name='event', - name='issue', - field=models.ForeignKey(to='issue.Issue'), - preserve_default=True, - ), - migrations.CreateModel( - name='Label', - fields=[ - ('id', models.AutoField(primary_key=True, verbose_name='ID', auto_created=True, serialize=False)), - ('name', models.CharField(max_length=32)), - ('deleted', models.BooleanField(default=False)), - ('color', colorful.fields.RGBColorField(verbose_name='Background color', default='#000000')), - ('inverted', models.BooleanField(verbose_name='Inverse text color', default=True)), - ], - options={ - }, - bases=(models.Model,), - ), - migrations.AddField( - model_name='issue', - name='labels', - field=models.ManyToManyField(blank=True, to='issue.Label', null=True), - preserve_default=True, - ), - migrations.CreateModel( - name='Milestone', - fields=[ - ('id', models.AutoField(primary_key=True, verbose_name='ID', auto_created=True, serialize=False)), - ('name', models.CharField(max_length=32, validators=[django.core.validators.RegexValidator(regex='^[a-z0-9_.-]+$', message='Please enter only lowercase characters, number, dot, underscores or hyphens.')])), - ('due_date', models.DateTimeField(blank=True, null=True)), - ('closed', models.BooleanField(default=False)), - ], - options={ - }, - bases=(models.Model,), - ), - migrations.AddField( - model_name='issue', - name='milestone', - field=models.ForeignKey(blank=True, to='issue.Milestone', null=True), - preserve_default=True, - ), - migrations.CreateModel( - name='Project', - fields=[ - ('name', models.CharField(primary_key=True, verbose_name='Short name (used in URL, definitive)', validators=[django.core.validators.RegexValidator(regex='^[a-z0-9_-]+$', message='Please enter only lowercase characters, number, underscores or hyphens.')], serialize=False, max_length=32)), - ('display_name', models.CharField(unique=True, verbose_name='Project name', max_length=32)), - ('description', models.TextField(verbose_name='Description', default='', blank=True)), - ('public', models.BooleanField(verbose_name='Do unregistered users have read access to this project?', default=True)), - ('subscribers', models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, null=True)), - ], - options={ - }, - bases=(models.Model,), - ), - migrations.AddField( - model_name='milestone', - name='project', - field=models.ForeignKey(to='issue.Project'), - preserve_default=True, - ), - migrations.AlterUniqueTogether( - name='milestone', - unique_together=set([('project', 'name')]), - ), - migrations.AddField( - model_name='label', - name='project', - field=models.ForeignKey(to='issue.Project'), - preserve_default=True, - ), - migrations.AddField( - model_name='issue', - name='project', - field=models.ForeignKey(to='issue.Project'), - preserve_default=True, - ), - migrations.AlterUniqueTogether( - name='issue', - unique_together=set([('project', 'id')]), - ), - migrations.CreateModel( - name='ProjectPermission', - fields=[ - ('id', models.AutoField(primary_key=True, verbose_name='ID', auto_created=True, serialize=False)), - ('grantee_type', models.IntegerField(choices=[(0, 'User'), (1, 'Group'), (2, 'Team')], verbose_name='Type', default=0)), - ('grantee_name', models.CharField(verbose_name='Name', max_length=50)), - ('manage_project_permission', models.BooleanField(default=False)), - ('create_issue', models.BooleanField(default=True)), - ('modify_issue', models.BooleanField(default=False)), - ('manage_issue', models.BooleanField(default=False)), - ('delete_issue', models.BooleanField(default=False)), - ('create_comment', models.BooleanField(default=True)), - ('modify_comment', models.BooleanField(default=False)), - ('delete_comment', models.BooleanField(default=False)), - ('manage_tags', models.BooleanField(default=False)), - ('delete_tags', models.BooleanField(default=False)), - ('project', models.ForeignKey(editable=False, to='issue.Project')), - ], - options={ - 'abstract': False, - }, - bases=(models.Model,), - ), - migrations.CreateModel( - name='Settings', - fields=[ - ('id', models.AutoField(primary_key=True, verbose_name='ID', auto_created=True, serialize=False)), - ('site', models.OneToOneField(to='sites.Site')), - ], - options={ - 'verbose_name_plural': 'Settings', - }, - bases=(models.Model,), - ), - migrations.CreateModel( - name='Team', - fields=[ - ('id', models.AutoField(primary_key=True, verbose_name='ID', auto_created=True, serialize=False)), - ('name', models.CharField(unique=True, max_length=128)), - ('groups', models.ManyToManyField(blank=True, to='auth.Group', null=True)), - ('users', models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, null=True)), - ], - options={ - }, - bases=(models.Model,), - ), - ] diff --git a/issue/migrations/0002_auto_20140823_0532.py b/issue/migrations/0002_auto_20140823_0532.py deleted file mode 100644 index c1e698b..0000000 --- a/issue/migrations/0002_auto_20140823_0532.py +++ /dev/null @@ -1,24 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import models, migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('issue', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='project', - name='access', - field=models.IntegerField(choices=[(1, 'Public'), (2, 'Connected users'), (3, 'Private')], default=1), - preserve_default=True, - ), - migrations.RemoveField( - model_name='project', - name='public', - ), - ] diff --git a/issue/templates/403.html b/issue/templates/403.html deleted file mode 100644 index 92266d9..0000000 --- a/issue/templates/403.html +++ /dev/null @@ -1,19 +0,0 @@ -{% extends 'base.html' %} - -{% block content %} - -
- -
-

- Permission denied -

-
- -
- Sorry, you are not allowed to access this page. -
- -
- -{% endblock %} diff --git a/issue/templates/404.html b/issue/templates/404.html deleted file mode 100644 index 19fb73c..0000000 --- a/issue/templates/404.html +++ /dev/null @@ -1,19 +0,0 @@ -{% extends 'base.html' %} - -{% block content %} - -
- -
-

- Page not found -

-
- -
- This is not the web page you are looking for. -
- -
- -{% endblock %} diff --git a/issue/templates/500.html b/issue/templates/500.html deleted file mode 100644 index 6a6c13a..0000000 --- a/issue/templates/500.html +++ /dev/null @@ -1,19 +0,0 @@ -{% extends 'base.html' %} - -{% block content %} - -
- -
-

- Server error -

-
- -
- Sorry, an error occured. Please try again in few minutes. -
- -
- -{% endblock %} diff --git a/issue/templates/base.html b/issue/templates/base.html deleted file mode 100644 index 0d59794..0000000 --- a/issue/templates/base.html +++ /dev/null @@ -1,123 +0,0 @@ -{% load staticfiles %} -{% load bootstrap3 %} - - - - - - - - - {% comment %}{% endcomment %} - - - - - {% block title %}PonyTracker{% endblock %} - - {% bootstrap_css %} - - {% block css %}{% endblock %} - - - {% block js %}{% endblock %} - - {% block media %}{% endblock %} - - - - -
- - - - -
- - {% bootstrap_messages %} - - {% block content %}{% endblock %} - - - -
- -
- - {% bootstrap_javascript %} - - {% block js_end %}{% endblock %} - - diff --git a/issue/templates/emails/close_issue.html b/issue/templates/emails/close_issue.html deleted file mode 100644 index 59a5557..0000000 --- a/issue/templates/emails/close_issue.html +++ /dev/null @@ -1,4 +0,0 @@ -Issue closed. - --- -You can see the issue on PonyTracker: {{ uri }} diff --git a/issue/templates/emails/new_comment.html b/issue/templates/emails/new_comment.html deleted file mode 100644 index 79512e7..0000000 --- a/issue/templates/emails/new_comment.html +++ /dev/null @@ -1,4 +0,0 @@ -{{ comment }} - --- -Respond on PonyTracker: {{ uri }} diff --git a/issue/templates/emails/new_issue.html b/issue/templates/emails/new_issue.html deleted file mode 100644 index c145060..0000000 --- a/issue/templates/emails/new_issue.html +++ /dev/null @@ -1,8 +0,0 @@ -{% if description %} -{{ description }} -{% else %} - No description. -{% endif %} - --- -Comment it on PonyTracker: {{ uri }} diff --git a/issue/templates/emails/reopen_issue.html b/issue/templates/emails/reopen_issue.html deleted file mode 100644 index 96d8a94..0000000 --- a/issue/templates/emails/reopen_issue.html +++ /dev/null @@ -1,4 +0,0 @@ -Issue reopened. - --- -You can see the issue on PonyTracker: {{ uri }} diff --git a/issue/templates/issue/global_permission_edit.html b/issue/templates/issue/global_permission_edit.html deleted file mode 100644 index 21fc207..0000000 --- a/issue/templates/issue/global_permission_edit.html +++ /dev/null @@ -1,29 +0,0 @@ -{% extends 'base.html' %} - -{% load bootstrap3 %} - -{% block content %} - -
- -
-

- Global permissions - -

- -
-
-
- {% bootstrap_form form %} - {% csrf_token %} - -
-
-
- -
- -{% endblock %} diff --git a/issue/templates/issue/global_permission_list.html b/issue/templates/issue/global_permission_list.html deleted file mode 100644 index f50c632..0000000 --- a/issue/templates/issue/global_permission_list.html +++ /dev/null @@ -1,89 +0,0 @@ -{% extends 'base.html' %} - -{% load django_markdown %} -{% load issue_filters %} -{% load issue_tags %} - -{% block content %} - - -
- -
-

- Global permissions - -

-
- - {% if permissions.count %} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {% for perm in permissions %} - - - - - - - - - - - - - - - - - - - - - - {% endfor %} -
TypeNameGlobalProject
ProjectTeamPermissionsIssuesCommentsTags
{% vertical 'Create?' %}{% vertical 'Modify?' %}{% vertical 'Delete?' %}{% vertical 'Create?' %}{% vertical 'Manage?' %}{% vertical 'Manage?' %}{% vertical 'Manage?' %}{% vertical 'Create?' %}{% vertical 'Manage?' %}{% vertical 'Modify?' %}{% vertical 'Delete?' %}{% vertical 'Create?' %}{% vertical 'Modify?' %}{% vertical 'Delete?' %}{% vertical 'Manage?' %}{% vertical 'Delete?' %}
{{ perm.type }}{{ perm.name }}{{ perm.create_project|boolean }}{{ perm.modify_project|boolean }}{{ perm.delete_project|boolean }}{{ perm.add_team|boolean }}{{ perm.manage_team|boolean }}{{ perm.manage_global_permission|boolean }}{{ perm.manage_project_permission|boolean }}{{ perm.create_issue|boolean }}{{ perm.manage_issue|boolean }}{{ perm.modify_issue|boolean }}{{ perm.delete_issue|boolean }}{{ perm.create_comment|boolean }}{{ perm.modify_comment|boolean }}{{ perm.delete_comment|boolean }}{{ perm.manage_tags|boolean }}{{ perm.delete_tags|boolean }} - Edit - Delete -
- {% else %} -
- There aren't any permissions defined yet. -
- {% endif %} - -{% endblock %} diff --git a/issue/templates/issue/issue_comment.html b/issue/templates/issue/issue_comment.html deleted file mode 100644 index 67d11fc..0000000 --- a/issue/templates/issue/issue_comment.html +++ /dev/null @@ -1,17 +0,0 @@ -{% extends 'issue/project.html' %} - -{% load bootstrap3 %} - -{% block media %}{{ form.media }}{% endblock %} - -{% block issuetab %} class="active"{% endblock %} - -{% block content %} - -
- {% bootstrap_form form %} - {% csrf_token %} - -
- -{% endblock %} diff --git a/issue/templates/issue/issue_edit.html b/issue/templates/issue/issue_edit.html deleted file mode 100644 index 2d47039..0000000 --- a/issue/templates/issue/issue_edit.html +++ /dev/null @@ -1,43 +0,0 @@ -{% extends 'issue/project.html' %} - -{% load bootstrap3 %} - -{% block issuetab %} class="active"{% endblock %} - -{% block media %} -{{ form.media }} -{% endblock media %} - -{% block content %} - -
- -
-

- {% if issue %} - Edit issue - {% else %} - New issue - {% endif %} -
- {% if issue %} - Go back to issue - {% else %} - Go back to issues - {% endif %} -
-

-
- -
-
-
- {% bootstrap_form form %} - {% csrf_token %} - -
-
-
- -
-{% endblock %} diff --git a/issue/templates/issue/issue_list.html b/issue/templates/issue/issue_list.html deleted file mode 100644 index 4681c22..0000000 --- a/issue/templates/issue/issue_list.html +++ /dev/null @@ -1,82 +0,0 @@ -{% extends 'issue/project.html' %} - -{% load humanize %} -{% load issue_tags %} - -{% block issuetab %} class="active"{% endblock %} - -{% block content %} - -
- -
-

- Issues -
-
-
-
- -
-
-
-
- -
- -
-
-
- {% if perm.create_issue %} -
-
- New issue -
-
- {% endif %} -
-
-

- {{ issues.count }} issue{{ issues.count|pluralize }} displayed -
- - {% if issues.count %} - - {% for issue in issues %} - - - - {% endfor %} -
- {% if issue.closed %} - - {% else %} - - {% endif %} - {{ issue }} - {% for label in issue.labels.all %} - {{ label }} - {% endfor %} -
- #{{ issue.id }} opened by {{ issue.author.username }} {{ issue.opened_at|naturaltime }} - {% if issue.milestone %} -  –   {{ issue.milestone }} - {% endif %} -  –  {{ issue.comments.count }} -
- {% else %} -
- No issues match your desired criteria. -
- {% endif %} - -
- -{% endblock %} diff --git a/issue/templates/issue/label_edit.html b/issue/templates/issue/label_edit.html deleted file mode 100644 index ab6c39e..0000000 --- a/issue/templates/issue/label_edit.html +++ /dev/null @@ -1,36 +0,0 @@ -{% extends 'issue/project.html' %} - -{% load bootstrap3 %} - -{% block labeltab %} class="active"{% endblock %} - -{% block content %} - -
- -
-

- {% if label %} - Edit label - {% else %} - New label - {% endif %} - -

-
- -
-
-
- {% bootstrap_form form %} - {% csrf_token %} - -
-
-
- -
- -{% endblock %} diff --git a/issue/templates/issue/label_list.html b/issue/templates/issue/label_list.html deleted file mode 100644 index d30b280..0000000 --- a/issue/templates/issue/label_list.html +++ /dev/null @@ -1,50 +0,0 @@ -{% extends 'issue/project.html' %} - -{% load issue_tags %} - -{% block labeltab %} class="active"{% endblock %} - -{% block content %} - -
- -
-

- Labels - {% if perm.manage_tags %} -
- New label -
- {% endif %} -

-
- - {% if labels.count %} - - {% for label in labels %} - - - - {% endfor %} -
- {{ label }} - {% if perm.manage_tags or perm.delete_tags %} -
- {% if perm.manage_tags %} - Edit - {% endif %} - {% if perm.delete_tags %} - Delete - {% endif %} -
- {% endif %} -
- {% else %} -
- There aren't any labels for this repository quite yet. -
- {% endif %} - -
- -{% endblock %} diff --git a/issue/templates/issue/milestone_edit.html b/issue/templates/issue/milestone_edit.html deleted file mode 100644 index e60b201..0000000 --- a/issue/templates/issue/milestone_edit.html +++ /dev/null @@ -1,40 +0,0 @@ -{% extends 'issue/project.html' %} - -{% load bootstrap3 %} - -{% block milestonetab %} class="active"{% endblock %} - -{% block media %} -{{ form.media }} -{% endblock %} - -{% block content %} - -
- -
-

- {% if milestone %} - Edit milestone - {% else %} - New milestone - {% endif %} - -

-
- -
-
-
- {% bootstrap_form form %} - {% csrf_token %} - -
-
-
- -
- -{% endblock %} diff --git a/issue/templates/issue/milestone_list.html b/issue/templates/issue/milestone_list.html deleted file mode 100644 index f086b9c..0000000 --- a/issue/templates/issue/milestone_list.html +++ /dev/null @@ -1,68 +0,0 @@ -{% extends 'issue/project.html' %} - -{% block milestonetab %} class="active"{% endblock %} - -{% block content %} - -
- -
-

- Milestone -
- Open - Close - All - {% if perm.manage_tags %} - New milestone - {% endif %} -
-

- {{ milestones.count }} milestone{{ milestones.count|pluralize }} displayed -
- - {% if milestones.count %} - - {% for milestone in milestones %} - - - - {% endfor %} -
-
- {% if perm.manage_tags %} - {% if milestone.closed %} - - {% else %} - - {% endif %} - - {% endif %} - {% if perm.delete_tags %} - Delete - {% endif %} -
- - {{ milestone }} - -   - - {% if milestone.due_date %}Due by {{ milestone.due_date }}{% else %}No due date{% endif %} - -

-
-
- {{ milestone.progress }}% complete -
-
-
- {% else %} -
- {{ no_milestone_message }} - There aren't any milestones matching your desired criteria. -
- {% endif %} - -
- -{% endblock %} diff --git a/issue/templates/issue/profile.html b/issue/templates/issue/profile.html deleted file mode 100644 index c445c18..0000000 --- a/issue/templates/issue/profile.html +++ /dev/null @@ -1,38 +0,0 @@ -{% extends 'base.html' %} - -{% load django_markdown %} - -{% block content %} - - -
- -
-

Profile

-
- -
-

Your groups

- {% if groups.exists %} -
    - {% for group in groups %} -
  • {{ group }}
  • - {% endfor %} -
- {% else %} - You belong to no groups. - {% endif %} -
-

Your teams

- {% if teams.exists %} -
    - {% for team in teams %} -
  • {{ team }}
  • - {% endfor %} -
- {% else %} - You belong to no teams. - {% endif %} -
- -{% endblock %} diff --git a/issue/templates/issue/project.html b/issue/templates/issue/project.html deleted file mode 100644 index 802da12..0000000 --- a/issue/templates/issue/project.html +++ /dev/null @@ -1,36 +0,0 @@ -{% extends 'base.html' %} - -{% block title %}{{ project }} - PonyTracker{% endblock %} -{% block page_title %}{{ project }}{% endblock %} - -{% block projectmenu %} -{% if request.user.is_authenticated or perm.manage_project_permission or perm.modify_project or perm.delete_project or perm.create_project %} - -{% if request.user in project.subscribers.all %} -
  • Unsubscribe
  • -{% else %} -
  • Subscribe
  • -{% endif %} -{% if perm.manage_project_permission %} -
  • Manage permissions
  • -{% endif %} -{% if perm.modify_project %} -
  • Modify this project
  • -{% endif %} -{% if perm.delete_project %} -
  • Delete this project
  • -{% endif %} -{% if perm.create_project %} -
  • New project…
  • -{% endif %} -{% endif %} -{% endblock %} - -{% block navbar %} -Issues -Labels -Milestones -{% if perm.manage_project_permission %} -Permissions -{% endif %} -{% endblock %} diff --git a/issue/templates/issue/project_add.html b/issue/templates/issue/project_add.html deleted file mode 100644 index 2d9ac0c..0000000 --- a/issue/templates/issue/project_add.html +++ /dev/null @@ -1,30 +0,0 @@ -{% extends 'base.html' %} - -{% load bootstrap3 %} - -{% block content %} - -
    - -
    -

    - New project - -

    -
    - -
    -
    -
    - {%bootstrap_form form %} - {% csrf_token %} - -
    -
    -
    - -
    - -{% endblock %} diff --git a/issue/templates/issue/project_edit.html b/issue/templates/issue/project_edit.html deleted file mode 100644 index ca05aac..0000000 --- a/issue/templates/issue/project_edit.html +++ /dev/null @@ -1,30 +0,0 @@ -{% extends 'issue/project.html' %} - -{% load bootstrap3 %} - -{% block content %} - -
    - -
    -

    - Edit project - -

    -
    - -
    -
    -
    - {% bootstrap_form form %} - {% csrf_token %} - -
    -
    -
    - -
    - -{% endblock %} diff --git a/issue/templates/issue/project_list.html b/issue/templates/issue/project_list.html deleted file mode 100644 index cc11f0c..0000000 --- a/issue/templates/issue/project_list.html +++ /dev/null @@ -1,53 +0,0 @@ -{% extends 'base.html' %} - -{% load django_markdown %} - -{% block content %} - - -
    - -
    -

    - Projects - {% if perm.create_project %} - - {% endif %} -

    -
    - -
    - {% if projects.exists %} -
    -
    - Name -
    -
    - Description -
    -
    - {% for project in projects %} -
    -
    - -
    - {% if project.description %} - {{ project.description|linebreaksbr }} - {% else %} - No description provided. - {% endif %} -
    -
    - {% endfor %} - {% elif user.is_authenticated %} - Sorry, you have no access to any project. - {% else %} - There is not any public project. You should probably login. - {% endif %} -
    - -{% endblock %} diff --git a/issue/templates/issue/project_permission_edit.html b/issue/templates/issue/project_permission_edit.html deleted file mode 100644 index e6f647e..0000000 --- a/issue/templates/issue/project_permission_edit.html +++ /dev/null @@ -1,31 +0,0 @@ -{% extends 'issue/project.html' %} - -{% load bootstrap3 %} - -{% block permissiontab %} class="active"{% endblock %} - -{% block content %} - -
    - -
    -

    - Permissions of '{{ project }}' project - -

    - -
    -
    -
    - {% bootstrap_form form %} - {% csrf_token %} - -
    -
    -
    - -
    - -{% endblock %} diff --git a/issue/templates/issue/project_permission_list.html b/issue/templates/issue/project_permission_list.html deleted file mode 100644 index e2fa976..0000000 --- a/issue/templates/issue/project_permission_list.html +++ /dev/null @@ -1,72 +0,0 @@ -{% extends 'issue/project.html' %} - -{% load django_markdown %} -{% load issue_filters %} -{% load issue_tags %} - -{% block permissiontab %} class="active"{% endblock %} - -{% block content %} - -
    - -
    -

    - Permissions of '{{ project }}' project - -

    -
    - - {% if permissions.count %} - - - - - - - - - - - - - - - - - - - - - - - {% for perm in permissions %} - - - - - - - - - - - - - - - - {% endfor %} -
    TypeNameIssuesCommentsLabels &
    Milestones
    Permissions
    {% vertical 'Create?' %}{% vertical 'Manage?' %}{% vertical 'Modify?' %}{% vertical 'Delete?' %}{% vertical 'Create?' %}{% vertical 'Modify?' %}{% vertical 'Delete?' %}{% vertical 'Manage?' %}{% vertical 'Delete?' %}{% vertical 'Manage?' %}
    {{ perm.type }}{{ perm.name }}{{ perm.create_issue|boolean }}{{ perm.manage_issue|boolean }}{{ perm.modify_issue|boolean }}{{ perm.delete_issue|boolean }}{{ perm.create_comment|boolean }}{{ perm.modify_comment|boolean }}{{ perm.delete_comment|boolean }}{{ perm.manage_tags|boolean }}{{ perm.delete_tags|boolean }}{{ perm.manage_project_permission|boolean }} - Edit - Delete -
    - {% else %} -
    - There aren't any permissions defined yet. -
    - {% endif %} - -{% endblock %} diff --git a/issue/templates/issue/team.html b/issue/templates/issue/team.html deleted file mode 100644 index a11884d..0000000 --- a/issue/templates/issue/team.html +++ /dev/null @@ -1,49 +0,0 @@ -{% extends 'base.html' %} - -{% block content %} - - -
    - -
    -

    - Teams - -

    -
    - -
    -

    Users

    - {% if team.users.exists %} -
      - {% for user in team.users.all %} -
    • {{ user }}
    • - {% endfor %} -
    - {% else %} - There aren't any users in this team. - {% endif %} -
    -

    Groups

    - {% if team.groups.exists %} -
      - {% for group in team.groups.all %} -
    • {{ group }}
    • - {% endfor %} -
    - {% else %} - There aren't any groups in this team. - {% endif %} -
    -
    - Go back to list - {% if perm.manage_team %} - Modify team - Delete team - {% endif %} -
    -
    - -{% endblock %} diff --git a/issue/templates/issue/team_edit.html b/issue/templates/issue/team_edit.html deleted file mode 100644 index 70c0149..0000000 --- a/issue/templates/issue/team_edit.html +++ /dev/null @@ -1,38 +0,0 @@ -{% extends 'base.html' %} - -{% load bootstrap3 %} - -{% block content %} - -
    - -
    -

    - {% if team %} - Edit team - {% else %} - Add team - {% endif %} -
    - {% if team %} - Go back to team - {% else %} - Go back to teams - {% endif %} -
    -

    -
    - -
    -
    -
    - {% bootstrap_form form %} - {% csrf_token %} - -
    -
    -
    - -
    - -{% endblock %} diff --git a/issue/templates/issue/team_list.html b/issue/templates/issue/team_list.html deleted file mode 100644 index d95b6b2..0000000 --- a/issue/templates/issue/team_list.html +++ /dev/null @@ -1,43 +0,0 @@ -{% extends 'base.html' %} - -{% load humanize %} -{% load issue_filters %} - -{% block content %} - - -
    - -
    -

    - Teams - {% if perm.add_team %} -
    - Add team -
    - {% endif %} -

    -
    - - {% if teams.exists %} - - - - - - - {% for team in teams %} - - - - - - {% endfor %} -
    NameUsersGroups
    {{ team }}{{ team.users|first_few:'user' }}{{ team.groups|first_few:'group' }}
    - {% else %} -
    - There aren't any teams quite yet. -
    - {% endif %} - -{% endblock %} diff --git a/issue/templates/login.html b/issue/templates/login.html deleted file mode 100644 index 18c3979..0000000 --- a/issue/templates/login.html +++ /dev/null @@ -1,30 +0,0 @@ -{% extends 'base.html' %} - -{% load bootstrap3 %} - -{% block content %} - -
    - -
    -

    - Login -
    - -
    -

    -
    - -
    -
    -
    - {% bootstrap_form form %} - {% csrf_token %} - -
    -
    -
    - -
    - -{% endblock %} diff --git a/issue/templatetags/issue_filters.py b/issue/templatetags/issue_filters.py deleted file mode 100644 index b6f3ca9..0000000 --- a/issue/templatetags/issue_filters.py +++ /dev/null @@ -1,31 +0,0 @@ -from django import template -from django.utils.safestring import mark_safe - - -register = template.Library() - - -@register.filter -def boolean(value): - if value: - glyph = 'ok' - else: - glyph = 'remove' - return mark_safe('') - - -@register.filter -def first_few(items, arg='item', max_items=5): - if items.exists(): - if items.count() <= max_items: - return ', '.join(map(lambda x: x.__str__(), items.all())) - else: - r = ', '.join(map(lambda x: x.__str__(), - items.all()[0:max_items - 1])) - plural = 's' if items.count() > max_items else '' - r += ', ... (%s other%s)' \ - % (items.count() - max_items + 1, plural) - return r - else: - return 'no ' + arg + 's' diff --git a/issue/tests.py b/issue/tests.py deleted file mode 100644 index cd6ea03..0000000 --- a/issue/tests.py +++ /dev/null @@ -1,621 +0,0 @@ -from django.test import TestCase, Client -from django import VERSION - -from issue.models import * - - -class TestPermissions(TestCase): - - fixtures = ['test_perms'] - - def test_team_user_membership(self): - user = User.objects.get(username='user1') - team = Team.objects.get(name='team1') - self.assertEqual(user.teams.count(), 1) - self.assertEqual(user.teams.first(), team) - - def test_team_group_membership(self): - user = User.objects.get(username='user2') - team = Team.objects.get(name='team2') - self.assertEqual(user.teams.count(), 1) - self.assertEqual(user.teams.first(), team) - - def test_global_no_perms(self): - user = User.objects.get(username='user4') - self.assertFalse(user.has_perm('create_project')) - self.assertFalse(user.has_perm('modify_project')) - self.assertFalse(user.has_perm('delete_project')) - - def test_global_user_perms(self): - user = User.objects.get(username='user3') - self.assertTrue(user.has_perm('create_project')) - self.assertFalse(user.has_perm('modify_project')) - self.assertFalse(user.has_perm('delete_project')) - - def test_global_group_perms(self): - user = User.objects.get(username='user2') - self.assertFalse(user.has_perm('create_project')) - self.assertTrue(user.has_perm('modify_project')) - self.assertFalse(user.has_perm('delete_project')) - - def test_global_team_perms(self): - user = User.objects.get(username='user1') - self.assertFalse(user.has_perm('create_project')) - self.assertFalse(user.has_perm('modify_project')) - self.assertTrue(user.has_perm('delete_project')) - - def test_project_no_perms(self): - user = User.objects.get(username='user4') - project = Project.objects.get(name='project-1') - self.assertFalse(user.has_perm('create_issue', project)) - self.assertFalse(user.has_perm('modify_issue', project)) - self.assertFalse(user.has_perm('delete_issue', project)) - - def test_project_user_perms(self): - user = User.objects.get(username='user3') - project = Project.objects.get(name='project-1') - self.assertTrue(user.has_perm('create_issue', project)) - self.assertFalse(user.has_perm('modify_issue', project)) - self.assertFalse(user.has_perm('delete_issue', project)) - - def test_project_group_perms(self): - user = User.objects.get(username='user2') - project = Project.objects.get(name='project-1') - self.assertFalse(user.has_perm('create_issue', project)) - self.assertTrue(user.has_perm('modify_issue', project)) - self.assertFalse(user.has_perm('delete_issue', project)) - - def test_project_team_perms(self): - user = User.objects.get(username='user1') - project = Project.objects.get(name='project-1') - self.assertFalse(user.has_perm('create_issue', project)) - self.assertFalse(user.has_perm('modify_issue', project)) - self.assertTrue(user.has_perm('delete_issue', project)) - - -class TestNoProject(TestCase): - - fixtures = ['test_no_project'] - - def test_ano(self): - url = reverse('list-project') - response = self.client.get(url) - self.assertEqual(response.status_code, 200) - self.assertContains(response, 'There is not any public project') - - def test_without_add_permission(self): - self.client.login(username='user1', password='user1') - url = reverse('list-project') - expected_url = reverse('add-project') - response = self.client.get(url) - self.assertEqual(response.status_code, 200) - self.assertContains(response, - 'Sorry, you have no access to any project') - - def test_with_add_permission(self): - self.client.login(username='user2', password='user2') - url = reverse('list-project') - expected_url = reverse('add-project') - response = self.client.get(url) - if VERSION >= (1, 7): - self.assertRedirects(response, expected_url, - # don't fetch redirect to don't loose message - fetch_redirect_response=False) - response = self.client.get(expected_url) - self.assertContains(response, 'Start by creating a project') - else: - self.assertRedirects(response, expected_url) - - -class TestGlobalViews(TestCase): - - fixtures = ['test_perms'] - - def test_404(self): - response = self.client.get('/deliberately/broken') - self.assertEqual(response.status_code, 404) - - def test_profile_ano(self): - url = reverse('profile') - expected_url = reverse('login') + '?next=' + url - response = self.client.get(url) - self.assertRedirects(response, expected_url) - - def test_profile_user1(self): - self.client.login(username='user1', password='user1') - url = reverse('profile') - response = self.client.get(url) - self.assertEqual(response.status_code, 200) - self.assertNotContains(response, 'group1') - self.assertContains(response, 'team1') - self.assertNotContains(response, 'team2') - - def test_profile_user2(self): - self.client.login(username='user2', password='user2') - url = reverse('profile') - response = self.client.get(url) - self.assertEqual(response.status_code, 200) - self.assertContains(response, 'group1') - self.assertNotContains(response, 'team1') - self.assertContains(response, 'team2') - - -class TestProjectsViews(TestCase): - - fixtures = ['test_perms'] - - def test_home_as_anonymous(self): - expected = Project.objects.filter(name='project-1') - url = reverse('list-project') - self.assertEqual(url, '/') - response = self.client.get(url) - self.assertEqual(response.status_code, 200) - self.assertQuerysetEqual(expected, response.context['projects'], - lambda x: x) - - def test_home_as_user1(self): - expected = Project.objects \ - .filter(Q(name='project-1') | Q(name='project-3')) - self.client.login(username='user1', password='user1') - url = reverse('list-project') - self.assertEqual(url, '/') - response = self.client.get(url) - self.assertEqual(response.status_code, 200) - self.assertQuerysetEqual(expected, response.context['projects'], - lambda x: x, ordered=False) - self.assertNotContains(response, 'New project') - - def test_home_as_user2(self): - expected = Project.objects.all() - self.client.login(username='user2', password='user2') - url = reverse('list-project') - self.assertEqual(url, '/') - response = self.client.get(url) - self.assertEqual(response.status_code, 200) - self.assertQuerysetEqual(expected, response.context['projects'], - lambda x: x, ordered=False) - self.assertNotContains(response, 'New project') - - def test_home_as_user3(self): - expected = Project.objects \ - .filter(Q(name='project-1') | Q(name='project-3')) - self.client.login(username='user3', password='user3') - url = reverse('list-project') - self.assertEqual(url, '/') - response = self.client.get(url) - self.assertEqual(response.status_code, 200) - self.assertQuerysetEqual(expected, response.context['projects'], - lambda x: x, ordered=False) - self.assertContains(response, 'New project') - - def test_home_as_admin(self): - expected = Project.objects.all() - self.client.login(username='admin', password='admin') - url = reverse('list-project') - self.assertEqual(url, '/') - response = self.client.get(url) - self.assertEqual(response.status_code, 200) - self.assertQuerysetEqual(expected, response.context['projects'], - lambda x: x, ordered=False) - self.assertContains(response, 'New project') - - def test_add_project_granted(self): - self.client.login(username='user3', password='user3') - expected_url = reverse('list-project-permission', args=['project-4']) - url = reverse('add-project') - response = self.client.post(url, { - 'name': 'project-4', - 'display_name': 'Project 4', - 'description': 'This is the fourth project.', - 'access': Project.ACCESS_PUBLIC, - }) - self.assertRedirects(response, expected_url) - self.assertQuerysetEqual(Project.objects.all(), ['project-%s' % x - for x in (1, 2, 3, 4)], lambda x: x.name, ordered=False) - - def test_add_project_forbidden(self): - self.client.login(username='user1', password='user1') - url = reverse('add-project') - response = self.client.post(url, { - 'name': 'project-4', - 'display_name': 'Project 4', - 'description': 'This is the foorth project.', - }) - self.assertEqual(response.status_code, 403) - self.assertQuerysetEqual(Project.objects.all(), ['project-%s' % x - for x in (1, 2, 3)], lambda x: x.name, ordered=False) - - def test_add_project_forbidden_ano(self): - expected_url = reverse('login') + '?next=' + reverse('add-project') - url = reverse('add-project') - response = self.client.post(url, { - 'name': 'project-4', - 'display_name': 'Project 4', - 'description': 'This is the foorth project.', - }) - self.assertRedirects(response, expected_url) - self.assertQuerysetEqual(Project.objects.all(), ['project-%s' % x - for x in (1, 2, 3)], lambda x: x.name, ordered=False) - - def test_delete_project_get(self): - self.client.login(username='user1', password='user1') - expected_url = reverse('list-project') - url = reverse('delete-project', args=['project-1']) - response = self.client.get(url) - self.assertEqual(response.status_code, 405) - self.assertQuerysetEqual(Project.objects.all(), ['project-%s' % x - for x in (1, 2, 3)], lambda x: x.name, ordered=False) - - def test_delete_project_granted(self): - self.client.login(username='user1', password='user1') - expected_url = reverse('list-project') - url = reverse('delete-project', args=['project-1']) - response = self.client.post(url) - self.assertRedirects(response, expected_url) - self.assertQuerysetEqual(Project.objects.all(), ['project-%s' % x - for x in (2, 3)], lambda x: x.name, ordered=False) - - def test_delete_project_forbidden(self): - self.client.login(username='user2', password='user2') - url = reverse('delete-project', args=['project-1']) - response = self.client.post(url) - self.assertEqual(response.status_code, 403) - self.assertQuerysetEqual(Project.objects.all(), ['project-%s' % x - for x in (1, 2, 3)], lambda x: x.name, ordered=False) - - def test_delete_project_forbidden_ano(self): - expected_url = reverse('login') + '?next=' \ - + reverse('delete-project', args=['project-1']) - url = reverse('delete-project', args=['project-1']) - response = self.client.post(url) - self.assertRedirects(response, expected_url) - self.assertQuerysetEqual(Project.objects.all(), ['project-%s' % x - for x in (1, 2, 3)], lambda x: x.name, ordered=False) - - -class TestIssuesViews(TestCase): - - fixtures = ['test_perms'] - - def test_list_issue_granted(self): - self.client.login(username='user2', password='user2') - url = reverse('list-issue', args=['project-2']) - response = self.client.get(url) - self.assertEqual(response.status_code, 200) - - def test_list_issue_forbidden(self): - self.client.login(username='user1', password='user1') - expected_url = reverse('login') + '?next=' \ - + reverse('list-issue', args=['project-2']) - url = reverse('list-issue', args=['project-2']) - response = self.client.get(url) - self.assertEqual(response.status_code, 403) - - def test_list_issue_forbidden_ano(self): - expected_url = reverse('login') + '?next=' \ - + reverse('list-issue', args=['project-2']) - url = reverse('list-issue', args=['project-2']) - response = self.client.get(url) - self.assertRedirects(response, expected_url) - - def test_show_issue_granted(self): - self.client.login(username='user2', password='user2') - url = reverse('show-issue', args=['project-2', 1]) - response = self.client.get(url) - self.assertEqual(response.status_code, 200) - - def test_show_issue_granted_ano(self): - url = reverse('show-issue', args=['project-1', 1]) - response = self.client.get(url) - self.assertEqual(response.status_code, 200) - - def test_show_issue_forbidden(self): - self.client.login(username='user1', password='user1') - expected_url = reverse('login') + '?next=' \ - + reverse('show-issue', args=['project-2', 1]) - url = reverse('show-issue', args=['project-2', 1]) - response = self.client.get(url) - self.assertEqual(response.status_code, 403) - - def test_add_issue_granted(self): - self.client.login(username='user5', password='user5') - expected_url = reverse('show-issue', args=['project-2', 3]) - url = reverse('add-issue', args=['project-2']) - response = self.client.post(url, { - 'title': 'Issue 3', - 'description': 'This is the third issue.', - }) - self.assertRedirects(response, expected_url) - issues = Issue.objects.filter(project__name='project-2') - self.assertQuerysetEqual(issues, ['Issue 1', 'Issue 2', 'Issue 3'], - lambda x: x.title, ordered=False) - - def test_add_issue_forbidden(self): - self.client.login(username='user6', password='user6') - url = reverse('add-issue', args=['project-2']) - response = self.client.post(url, { - 'title': 'Issue 3', - 'description': 'This is the third issue.', - }) - self.assertEqual(response.status_code, 403) - issues = Issue.objects.filter(project__name='project-2') - self.assertQuerysetEqual(issues, ['Issue 1', 'Issue 2'], - lambda x: x.title, ordered=False) - - def test_add_issue_forbidden_ano(self): - expected_url = reverse('login') + '?next=' \ - + reverse('add-issue', args=['project-2']) - url = reverse('add-issue', args=['project-2']) - response = self.client.post(url, { - 'title': 'Issue 3', - 'description': 'This is the third issue.', - }) - self.assertRedirects(response, expected_url) - issues = Issue.objects.filter(project__name='project-2') - self.assertQuerysetEqual(issues, ['Issue 1', 'Issue 2'], - lambda x: x.title, ordered=False) - - def test_delete_issue_get(self): - self.client.login(username='user8', password='user8') - expected_url = reverse('list-issue', args=['project-2']) - url = reverse('delete-issue', args=['project-2', 2]) - response = self.client.get(url) - self.assertEqual(response.status_code, 405) - issues = Issue.objects.filter(project__name='project-2') - self.assertQuerysetEqual(issues, ['Issue 1', 'Issue 2'], - lambda x: x.title, ordered=False) - - def test_delete_issue_granted(self): - self.client.login(username='user8', password='user8') - expected_url = reverse('list-issue', args=['project-2']) - url = reverse('delete-issue', args=['project-2', 2]) - response = self.client.post(url) - self.assertRedirects(response, expected_url) - issues = Issue.objects.filter(project__name='project-2') - self.assertQuerysetEqual(issues, ['Issue 1'], - lambda x: x.title, ordered=False) - - def test_delete_issue_forbidden(self): - self.client.login(username='user5', password='user5') - url = reverse('delete-issue', args=['project-2', 2]) - response = self.client.post(url) - self.assertEqual(response.status_code, 403) - issues = Issue.objects.filter(project__name='project-2') - self.assertQuerysetEqual(issues, ['Issue 1', 'Issue 2'], - lambda x: x.title, ordered=False) - - def test_delete_issue_forbidden_ano(self): - expected_url = reverse('login') + '?next=' \ - + reverse('delete-issue', args=['project-2', 2]) - url = reverse('delete-issue', args=['project-2', 2]) - response = self.client.post(url) - self.assertRedirects(response, expected_url) - issues = Issue.objects.filter(project__name='project-2') - self.assertQuerysetEqual(issues, ['Issue 1', 'Issue 2'], - lambda x: x.title, ordered=False) - - def test_close_issue_granted(self): - self.client.login(username='user6', password='user6') - expected_url = reverse('list-issue', args=['project-2']) - url = reverse('close-issue', args=['project-2', 1]) - response = self.client.get(url) - self.assertRedirects(response, expected_url) - issue = Issue.objects.get(project__name='project-2', id=1) - self.assertEqual(issue.closed, True) - - def test_close_issue_forbidden(self): - self.client.login(username='user5', password='user5') - url = reverse('close-issue', args=['project-2', 1]) - response = self.client.get(url) - self.assertEqual(response.status_code, 403) - issue = Issue.objects.get(project__name='project-2', id=1) - self.assertEqual(issue.closed, False) - - def test_close_issue_forbidden_ano(self): - expected_url = reverse('login') + '?next=' \ - + reverse('close-issue', args=['project-2', 1]) - url = reverse('close-issue', args=['project-2', 1]) - response = self.client.get(url) - self.assertRedirects(response, expected_url) - issue = Issue.objects.get(project__name='project-2', id=1) - self.assertEqual(issue.closed, False) - - def test_reopen_issue_granted(self): - self.client.login(username='user6', password='user6') - expected_url = reverse('show-issue', args=['project-2', 2]) - url = reverse('reopen-issue', args=['project-2', 2]) - response = self.client.get(url) - self.assertRedirects(response, expected_url) - issue = Issue.objects.get(project__name='project-2', id=2) - self.assertEqual(issue.closed, False) - - def test_reopen_issue_forbidden(self): - self.client.login(username='user5', password='user5') - url = reverse('reopen-issue', args=['project-2', 2]) - response = self.client.get(url) - self.assertEqual(response.status_code, 403) - issue = Issue.objects.get(project__name='project-2', id=2) - self.assertEqual(issue.closed, True) - - def test_reopen_issue_forbidden_ano(self): - expected_url = reverse('login') + '?next=' \ - + reverse('reopen-issue', args=['project-2', 2]) - url = reverse('reopen-issue', args=['project-2', 2]) - response = self.client.get(url) - self.assertRedirects(response, expected_url) - issue = Issue.objects.get(project__name='project-2', id=2) - self.assertEqual(issue.closed, True) - - def test_modify_issue_granted(self): - self.client.login(username='user7', password='user7') - expected_url = reverse('show-issue', args=['project-2', 1]) - url = reverse('edit-issue', args=['project-2', 1]) - response = self.client.post(url, { - 'title': '*THE* Issue 1', - 'description': 'This is *THE* first issue.', - }) - self.assertRedirects(response, expected_url) - issue = Issue.objects.get(project__name='project-2', id=1) - self.assertEqual(issue.title, "*THE* Issue 1") - self.assertEqual(issue.description, "This is *THE* first issue.") - - def test_modify_issue_forbidden(self): - self.client.login(username='user5', password='user5') - url = reverse('edit-issue', args=['project-2', 1]) - response = self.client.post(url, { - 'title': '*THE* Issue 1', - 'description': 'This is *THE* first issue.', - }) - self.assertEqual(response.status_code, 403) - issue = Issue.objects.get(project__name='project-2', id=1) - self.assertEqual(issue.title, "Issue 1") - self.assertEqual(issue.description, "This is the first issue.") - - def test_modify_issue_forbidden_ano(self): - expected_url = reverse('login') + '?next=' \ - + reverse('edit-issue', args=['project-2', 1]) - url = reverse('edit-issue', args=['project-2', 1]) - response = self.client.post(url, { - 'title': '*THE* Issue 1', - 'description': 'This is *THE* first issue.', - }) - self.assertRedirects(response, expected_url) - issue = Issue.objects.get(project__name='project-2', id=1) - self.assertEqual(issue.title, "Issue 1") - self.assertEqual(issue.description, "This is the first issue.") - - -class TestCommentsViews(TestCase): - - fixtures = ['test_perms'] - - def test_comment_issue_granted(self): - self.client.login(username='user9', password='user9') - msg = 'I have a lot to say.' - expected_url = reverse('show-issue', args=['project-2', 1]) - url = reverse('comment-issue', args=['project-2', 1]) - response = self.client.post(url, { - 'comment': msg, - }) - self.assertRedirects(response, expected_url) - issue = Issue.objects.get(project__name='project-2', id=1) - event = Event.objects.filter(issue=issue, code=Event.COMMENT).last() - self.assertEqual(event.additionnal_section, msg) - - def test_comment_issue_forbidden(self): - self.client.login(username='user10', password='user10') - msg = 'I have a lot to say.' - url = reverse('comment-issue', args=['project-2', 1]) - response = self.client.post(url, { - 'comment': msg, - }) - self.assertEqual(response.status_code, 403) - issue = Issue.objects.get(project__name='project-2', id=1) - event = Event.objects.filter(issue=issue, code=Event.COMMENT).last() - self.assertEqual(event.additionnal_section, 'Missing things') - - def test_edit_comment_granted(self): - self.client.login(username='user10', password='user10') - msg = 'Missing a lot of things' - expected_url = reverse('show-issue', args=['project-2', 1]) - issue = Issue.objects.get(project__name='project-2', id=1) - event = Event.objects.filter(issue=issue, code=Event.COMMENT).last() - url = reverse('edit-comment', args=['project-2', issue.id, event.id]) - response = self.client.post(url, { - 'comment': msg, - }) - self.assertRedirects(response, expected_url) - issue = Issue.objects.get(project__name='project-2', id=1) - event = Event.objects.filter(issue=issue, code=Event.COMMENT).last() - self.assertEqual(event.additionnal_section, msg) - - def test_edit_comment_forbidden(self): - self.client.login(username='user9', password='user9') - msg = 'Missing a lot of things' - issue = Issue.objects.get(project__name='project-2', id=1) - event = Event.objects.filter(issue=issue, code=Event.COMMENT).last() - url = reverse('edit-comment', args=['project-2', issue.id, event.id]) - response = self.client.post(url, { - 'comment': msg, - }) - self.assertEqual(response.status_code, 403) - - def test_delete_comment_granted_get(self): - self.client.login(username='user11', password='user11') - issue = Issue.objects.get(project__name='project-2', id=1) - event = Event.objects.filter(issue=issue, code=Event.COMMENT).last() - url = reverse('delete-comment', args=['project-2', issue.id, event.id]) - response = self.client.get(url) - self.assertEqual(response.status_code, 405) - issue = Issue.objects.get(project__name='project-2', id=1) - event = Event.objects.filter(issue=issue, code=Event.COMMENT).last() - self.assertEqual(event.additionnal_section, 'Missing things') - - def test_delete_comment_granted(self): - self.client.login(username='user11', password='user11') - issue = Issue.objects.get(project__name='project-2', id=1) - event = Event.objects.filter(issue=issue, code=Event.COMMENT).last() - url = reverse('delete-comment', args=['project-2', issue.id, event.id]) - expected_url = reverse('show-issue', args=['project-2', 1]) - response = self.client.post(url) - self.assertRedirects(response, expected_url) - issue = Issue.objects.get(project__name='project-2', id=1) - event = Event.objects.filter(issue=issue, code=Event.COMMENT).last() - self.assertEqual(event.additionnal_section, 'Done') - - def test_delete_comment_forbidden(self): - self.client.login(username='user9', password='user9') - msg = 'Missing a lot of things' - issue = Issue.objects.get(project__name='project-2', id=1) - event = Event.objects.filter(issue=issue, code=Event.COMMENT).last() - url = reverse('delete-comment', args=['project-2', issue.id, event.id]) - response = self.client.post(url, { - 'comment': msg, - }) - self.assertEqual(response.status_code, 403) - issue = Issue.objects.get(project__name='project-2', id=1) - event = Event.objects.filter(issue=issue, code=Event.COMMENT).last() - self.assertEqual(event.additionnal_section, 'Missing things') - - -class TestLabelsViews(TestCase): - - fixtures = ['test_perms'] - - def test_list(self): - self.client.login(username='user2', password='user2') - url = reverse('list-label', args=['project-2']) - response = self.client.get(url) - self.assertEqual(response.status_code, 200) - self.assertContains(response, 'documentation') - - -class TestMilestonesViews(TestCase): - - fixtures = ['test_perms'] - - def test_list(self): - self.client.login(username='user2', password='user2') - url = reverse('list-milestone', args=['project-2']) - response = self.client.get(url) - self.assertEqual(response.status_code, 200) - self.assertContains(response, 'v1.0') - - -class TestPermissionsViews(TestCase): - - fixtures = ['test_perms'] - - def test_global_list(self): - self.client.login(username='user15', password='user15') - url = reverse('list-global-permission') - response = self.client.get(url) - self.assertEqual(response.status_code, 200) - self.assertContains(response, 'Global permissions') - - def test_project_list(self): - self.client.login(username='user14', password='user14') - url = reverse('list-project-permission', args=['project-2']) - response = self.client.get(url) - self.assertEqual(response.status_code, 200) - self.assertContains(response, "Permissions of 'Project 2' project") diff --git a/issue/urls.py b/issue/urls.py deleted file mode 100644 index 89bb0ca..0000000 --- a/issue/urls.py +++ /dev/null @@ -1,58 +0,0 @@ -from django.conf.urls import url - -urlpatterns = [ - url(r'^$', 'issue.views.project_list', name='list-project'), - url(r'^add$', 'issue.views.project_add', name='add-project'), - url(r'^(?P[a-z0-9_-]+)/edit$', 'issue.views.project_edit', name='edit-project'), - url(r'^(?P[a-z0-9_-]+)/delete$', 'issue.views.project_delete', name='delete-project'), - url(r'^(?P[a-z0-9_-]+)/subscribe$', 'issue.views.project_subscribe', name='subscribe-project'), - url(r'^(?P[a-z0-9_-]+)/unsubscribe$', 'issue.views.project_unsubscribe', name='unsubscribe-project'), - url(r'^(?P[a-z0-9_-]+)/issues$', 'issue.views.issue_list', name='list-issue'), - url(r'^(?P[a-z0-9_-]+)/issues/add$', 'issue.views.issue_edit', name='add-issue'), - url(r'^(?P[a-z0-9_-]+)/issues/(?P[0-9]+)$', 'issue.views.issue', name='show-issue'), - url(r'^(?P[a-z0-9_-]+)/issues/(?P[0-9]+)/edit$', 'issue.views.issue_edit', name='edit-issue'), - url(r'^(?P[a-z0-9_-]+)/issues/(?P[0-9]+)/close$', 'issue.views.issue_close', name='close-issue'), - url(r'^(?P[a-z0-9_-]+)/issues/(?P[0-9]+)/reopen$', 'issue.views.issue_reopen', name='reopen-issue'), - url(r'^(?P[a-z0-9_-]+)/issues/(?P[0-9]+)/comment$', 'issue.views.issue_edit_comment', name='comment-issue'), - url(r'^(?P[a-z0-9_-]+)/issues/(?P[0-9]+)/comments/(?P[0-9]+)/edit$', 'issue.views.issue_edit_comment', name='edit-comment'), - url(r'^(?P[a-z0-9_-]+)/issues/(?P[0-9]+)/comments/(?P[0-9]+)/delete$', 'issue.views.issue_delete_comment', name='delete-comment'), - url(r'^(?P[a-z0-9_-]+)/issues/(?P[0-9]+)/delete$', 'issue.views.issue_delete', name='delete-issue'), - url(r'^(?P[a-z0-9_-]+)/issues/(?P[0-9]+)/subscribe$', 'issue.views.issue_subscribe', name='subscribe-issue'), - url(r'^(?P[a-z0-9_-]+)/issues/(?P[0-9]+)/unsubscribe$', 'issue.views.issue_unsubscribe', name='unsubscribe-issue'), - url(r'^(?P[a-z0-9_-]+)/issues/(?P[0-9]+)/add-label/(?P