Skip to content
This repository was archived by the owner on Jun 1, 2022. It is now read-only.

Commit 5b290bc

Browse files
committed
Merge pull request #204 from editorsnotes/account-creation-fixes
Account creation fixes-- adding projects; creating roles; assigning permissions
2 parents abcee94 + ee106a1 commit 5b290bc

File tree

20 files changed

+1508
-140
lines changed

20 files changed

+1508
-140
lines changed

editorsnotes/admin_custom/forms/projects.py

Lines changed: 93 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
from django import forms
22
from django.contrib.auth.models import Group
33
from django.forms.models import (
4-
BaseModelFormSet, ModelForm, modelformset_factory)
4+
BaseModelFormSet, ModelForm, modelformset_factory, ValidationError)
55

6-
from editorsnotes.main.models import User, Project, ProjectInvitation
6+
from editorsnotes.main.management import get_all_project_permissions
7+
from editorsnotes.main.models import (
8+
User, Project, ProjectInvitation, ProjectRole)
79

810
class ProjectRoleField(forms.ModelChoiceField):
911
def label_from_instance(self, obj):
@@ -21,7 +23,9 @@ def make_project_invitation_formset(project):
2123
if not isinstance(project, Project):
2224
raise ValueError('{} is not a project.'.format(project))
2325

24-
project_invitations = ProjectInvitation.objects.filter(project_id=project.id)
26+
project_invitations = ProjectInvitation.objects\
27+
.select_related('user', 'role')\
28+
.filter(project_id=project.id)
2529

2630
class ProjectInvitationFormSet(BaseModelFormSet):
2731
def __init__(self, *args, **kwargs):
@@ -75,7 +79,7 @@ def clean_email(self):
7579

7680
def make_project_roster_formset(project):
7781
"""
78-
Returns a formset for editing project roles.
82+
Returns a formset for editing project members' roles.
7983
"""
8084
if not isinstance(project, Project):
8185
raise ValueError('{} is not a project.'.format(project))
@@ -117,8 +121,93 @@ def save(self, commit=True):
117121
extra=0
118122
)
119123

124+
def make_project_permissions_formset(project):
125+
roles = project.roles.all()
126+
project_perms= get_all_project_permissions().order_by('content_type')
127+
128+
class ProjectRoleFormSet(BaseModelFormSet):
129+
def __init__(self, *args, **kwargs):
130+
kwargs['queryset'] = roles
131+
super(ProjectRoleFormSet, self).__init__(*args, **kwargs)
132+
133+
class ProjectRoleForm(ModelForm):
134+
permissions = forms.ModelMultipleChoiceField(
135+
required=False,
136+
queryset=project_perms,
137+
widget=forms.CheckboxSelectMultiple)
138+
class Meta:
139+
model = ProjectRole
140+
fields = ('role', 'permissions',)
141+
def __init__(self, *args, **kwargs):
142+
super(ProjectRoleForm, self).__init__(*args, **kwargs)
143+
if self.instance and self.instance.is_super_role:
144+
self.fields['permissions'].widget.attrs['readonly'] = True
145+
self.fields['permissions'].widget.attrs['checked'] = True
146+
elif self.instance and self.instance.id:
147+
self.fields['permissions'].initial = self.instance.group.permissions.all()
148+
def save(self, *args, **kwargs):
149+
# We are NOT committing the change here, because we only save the
150+
# form if the object already exists. This is because it was
151+
# difficult to deal with the M2M relations (permissions => the new
152+
# group which is created along with the project role) when doing
153+
# that. We are calling save, however, to bound this form to an
154+
# instance if it exists, so that we can proceed in the right way
155+
# afterwards (create a ProjectRole manually if no existing instance;
156+
# save form otherwise).
157+
kwargs['commit'] = False
158+
obj = super(ProjectRoleForm, self).save(*args, **kwargs)
159+
160+
perms = self.cleaned_data['permissions']
161+
if not obj.id:
162+
new_role = ProjectRole.objects.create_project_role(
163+
project, self.cleaned_data['role'])
164+
new_role.group.permissions.add(*perms)
165+
else:
166+
obj.save()
167+
obj.group.permissions.clear()
168+
obj.group.permissions.add(*perms)
169+
return obj
170+
171+
formset = modelformset_factory(ProjectRole,
172+
formset=ProjectRoleFormSet,
173+
form=ProjectRoleForm,
174+
can_delete=True,
175+
extra=1)
176+
formset.all_perms = project_perms
177+
return formset
178+
179+
180+
BANNED_PROJECT_SLUGS = (
181+
'add',
182+
)
183+
120184
class ProjectForm(ModelForm):
121185
class Meta:
122186
model = Project
123187
exclude = ('slug',)
124188

189+
class ProjectCreationForm(ModelForm):
190+
"""
191+
Form for creating a project. Must be passed a user.
192+
"""
193+
join_project = forms.BooleanField(initial=True,
194+
help_text='Join project after creation?')
195+
class Meta:
196+
model = Project
197+
def __init__(self, *args, **kwargs):
198+
user = kwargs.pop('user')
199+
super(ProjectCreationForm, self).__init__(*args, **kwargs)
200+
self.user = user
201+
def clean_slug(self):
202+
data = self.cleaned_data['slug']
203+
if data in BANNED_PROJECT_SLUGS:
204+
raise ValidationError('This slug is a reserved word.')
205+
return data
206+
def save(self, *args, **kwargs):
207+
obj = super(ProjectCreationForm, self).save()
208+
if self.cleaned_data['join_project']:
209+
role = obj.roles.get(role='Editor')
210+
role.users.add(self.user)
211+
return obj
212+
213+

editorsnotes/admin_custom/templates/featured_items_admin.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ <h5>Add an item</h5>
3838
<option value="documents">Document</option>
3939
<option value="topics">Topic</option>
4040
</select>
41-
<input type="text" placeholder="Type to search" search-project-id={{ project.id }} class="span5 search-autocomplete autocomplete-no-redirect"></input>
41+
<input type="text" placeholder="Type to search" data-project-slug={{ project.slug }} class="span5 search-autocomplete autocomplete-no-redirect"></input>
4242
{% else %}
4343
<p>You can only designate up to five featured items. Remove at least one item to add another.</p>
4444
{% endif %}
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
{% extends "base.html" %}
2+
3+
{% block js %}
4+
<script type="text/javascript">
5+
(function () {
6+
7+
function addFormToTable($el, $table) {
8+
var $input = $el.find('input[type="text"]')
9+
, $checkboxes = $el.find('input[type="checkbox"][name*="permissions"]')
10+
, $deleteBox = $el.find('input[name*="DELETE"]')
11+
, $rows = $table.find('tbody tr')
12+
, $header
13+
, $footer
14+
15+
$header = $('<th>')
16+
.append($input.hide())
17+
.append($input.val())
18+
.appendTo( $table.find('thead tr') )
19+
20+
if ( $input.val() == 'Editor' ) {
21+
$header.addClass('editor-role');
22+
}
23+
24+
$footer = $header.clone().appendTo( $table.find('tfoot tr') );
25+
26+
$footer.find('input').remove();
27+
28+
$('tfoot th:last:not(.editor-role)')
29+
.append('<button class="btn btn-danger" type="button">Delete</button>')
30+
.find('button')
31+
.css({
32+
'display': 'block',
33+
'position': 'relative',
34+
'margin': '16px auto -44px'
35+
})
36+
.on('click', function () {
37+
var $tds = $checkboxes.closest('td').add($footer).add($header);
38+
39+
if ($deleteBox.prop('checked')) {
40+
$tds.removeClass('to-delete');
41+
$deleteBox.prop('checked', false)
42+
$(this).text('Delete');
43+
} else {
44+
$tds.addClass('to-delete');
45+
$deleteBox.prop('checked', true)
46+
$(this).text('Undo');
47+
}
48+
})
49+
50+
51+
$checkboxes.each(function (idx, el) {
52+
$('<td>').append(el).appendTo( $rows.eq(idx) );
53+
});
54+
55+
}
56+
57+
$(document).ready(function () {
58+
var $table = $('table')
59+
60+
$('.permission-form').each(function (idx, el) {
61+
addFormToTable($(el), $table);
62+
});
63+
64+
$('<button class="btn btn-primary">Add role</button>')
65+
.appendTo('body')
66+
.css({ 'padding': '7px 16px', 'width': '100px' })
67+
.position({
68+
'my': 'left+12 top+12',
69+
'at': 'right',
70+
'of': $table.find('thead')
71+
})
72+
.on('click', function () {
73+
$(this).remove();
74+
$table.find('th:last-of-type, th:last-of-type input, td:last-of-type').show();
75+
});
76+
77+
});
78+
79+
})()
80+
</script>
81+
{% endblock %}
82+
83+
{% block css %}
84+
<style type="text/css">
85+
#perms-table {
86+
width: auto;
87+
}
88+
td:first-of-type {
89+
width: 180px;
90+
}
91+
td:not(:first-of-type), th:not(:first-of-type) {
92+
text-align: center;
93+
min-width: 100px;
94+
}
95+
th input[type="text"] {
96+
margin: 0;
97+
text-align: center;
98+
}
99+
#perms-table tr:hover td {
100+
background: #fffacd;
101+
}
102+
#perms-table th {
103+
vertical-align: middle;
104+
}
105+
.hide-extra td:last-of-type, .hide-extra th:last-of-type {
106+
display: none;
107+
}
108+
109+
td.to-delete, th.to-delete {
110+
background: #fa8072 !important;
111+
}
112+
</style>
113+
{% endblock %}
114+
115+
{% block content %}
116+
117+
<ul class="breadcrumb-top">
118+
<li>
119+
<a href="{% url "project_view" project_slug=project.slug %}">{{ project }}</a>
120+
<span class="divider">&gt;</span>
121+
</li>
122+
<li>
123+
<a href="{% url "admin:main_project_roster_change" project_slug=project.slug %}">Roster</a>
124+
<span class="divider">&gt;</span>
125+
</li>
126+
<li class="active">Roles</li>
127+
</ul>
128+
129+
<h1>Change project roles</h1>
130+
{% include "includes/bootstrap_errors.html" with errors=formset.errors %}
131+
<form method="post">
132+
{% csrf_token %}
133+
<table id="perms-table" class="table table-condensed table-bordered table-striped hide-extra">
134+
<thead>
135+
<tr>
136+
<th></th>
137+
</tr>
138+
</thead>
139+
<tbody>
140+
{% for perm in formset.all_perms %}
141+
<tr>
142+
<td>{{ perm.name }}</td>
143+
</tr>
144+
{% endfor %}
145+
</tbody>
146+
<tfoot>
147+
<tr>
148+
<th></th>
149+
</tr>
150+
</tfoot>
151+
</table>
152+
<div style="display: none;">
153+
{{ formset.management_form }}
154+
{% for form in formset.forms %}
155+
<div class="permission-form">{{ form }}</div>
156+
{% endfor %}
157+
</div>
158+
<button type="submit" class="btn">Save roles</button>
159+
</form>
160+
161+
{% endblock %}

0 commit comments

Comments
 (0)