Removed superusers from roles edit page. It didn't have any sense to view
[e_cidadania.git] / src / core / spaces / views / spaces.py
blob8e690b93c1e5289fd92a0bd0fa9f578d7c0fa753
1 # -*- coding: utf-8 -*-
3 # Copyright (c) 2010-2013 Cidadania S. Coop. Galega
5 # This file is part of e-cidadania.
7 # e-cidadania is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
12 # e-cidadania is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with e-cidadania. If not, see <http://www.gnu.org/licenses/>.
20 from django.views.generic.list import ListView
21 from django.views.generic.edit import UpdateView, DeleteView
22 from django.views.generic.detail import DetailView
23 from django.contrib.auth.decorators import login_required
24 from django.shortcuts import render_to_response, get_object_or_404
25 from django.contrib import messages
26 from django.template import RequestContext
27 from django.utils.translation import ugettext_lazy as _
28 from django.contrib.comments.models import Comment
29 from django.db.models import Count
30 from django.core.urlresolvers import reverse
31 from django.core.exceptions import PermissionDenied
32 from django.http import HttpResponseRedirect, HttpResponse
33 from django.contrib.auth.models import User
35 from helpers.cache import get_or_insert_object_in_cache
36 from operator import itemgetter
37 from guardian.shortcuts import assign_perm, get_users_with_perms, remove_perm, get_perms
38 from guardian.core import ObjectPermissionChecker
40 from core.spaces import url_names as urln
41 from core.spaces.models import Space, Entity, Document, Event
42 from core.spaces.forms import SpaceForm, EntityFormSet, RoleForm
43 from apps.ecidadania.news.models import Post
44 from apps.ecidadania.proposals.models import Proposal, ProposalSet
45 from apps.ecidadania.staticpages.models import StaticPage
46 from apps.ecidadania.debate.models import Debate
47 from apps.ecidadania.voting.models import Poll, Voting
48 from e_cidadania.settings import DEBUG
51 # Please take in mind that the create_space view can't be replaced by a CBV
52 # (class-based view) since it manipulates two forms at the same time. Apparently
53 # that creates some trouble in the django API. See this ticket:
54 # https://code.djangoproject.com/ticket/16256
55 @login_required
56 def create_space(request):
58 """
59 Returns a SpaceForm form to fill with data to create a new space. There
60 is an attached EntityFormset to save the entities related to the space.
61 Only site administrators are allowed to create spaces.
63 .. note:: Since everyone can have the ability to create spaces, instead
64 of checking for the add_space permission we just ask for login.
66 :attributes: - space_form: empty SpaceForm instance
67 - entity_forms: empty EntityFormSet
68 :permissions required: login_required
69 :rtype: Space object, multiple entity objects.
70 :context: form, entityformset
71 """
72 space_form = SpaceForm(request.POST or None, request.FILES or None)
73 entity_forms = EntityFormSet(request.POST or None, request.FILES or None,
74 queryset=Entity.objects.none())
76 if request.method == 'POST':
77 if space_form.is_valid() and entity_forms.is_valid():
78 space_form_uncommited = space_form.save(commit=False)
79 space_form_uncommited.author = request.user
81 new_space = space_form_uncommited.save()
82 space = get_object_or_404(Space, name=space_form_uncommited.name)
84 ef_uncommited = entity_forms.save(commit=False)
85 for ef in ef_uncommited:
86 ef.space = space
87 ef.save()
89 # We add the created spaces to the user allowed spaces
90 # space.admins.add(request.user)
91 space_form.save_m2m()
93 # Assign permissions to the user so he can chenge everything in the
94 # space
95 assign_perm('view_space', request.user, space)
96 assign_perm('change_space', request.user, space)
97 assign_perm('delete_space', request.user, space)
98 assign_perm('admin_space', request.user, space)
100 if DEBUG:
101 # This will tell us if the user got the right permissions for
102 # the object
103 un = request.user.username
104 u = ObjectPermissionChecker(request.user) # Avoid unnecesary queries for the permission checks
105 print """Space permissions for user '%s':
106 View: %s
107 Change: %s
108 Delete: %s
109 Admin: %s
110 Mod: %s
111 """ % (un, u.has_perm('view_space', space),
112 u.has_perm('change_space', space),
113 u.has_perm('delete_space', space),
114 u.has_perm('admin_space', space),
115 u.has_perm('mod_space', space))
117 return HttpResponseRedirect(reverse(urln.SPACE_INDEX,
118 kwargs={'space_url': space.url}))
120 return render_to_response('spaces/space_form.html',
121 {'form': space_form,
122 'entityformset': entity_forms},
123 context_instance=RequestContext(request))
126 class ViewSpaceIndex(DetailView):
129 Returns the index page for a space. The access to spaces is restricted and
130 filtered in the get_object method. This view gathers information from all
131 the configured modules in the space.
133 :attributes: space_object, place
134 :permissions required: space.view_space
135 :rtype: Object
136 :context: get_place, entities, documents, proposals, publication
138 context_object_name = 'get_place'
139 template_name = 'spaces/space_index.html'
141 def dispatch(self, request, *args, **kwargs):
143 We get the current space and user, first we check if the space is
144 public, if so, we check if the user is anonymous and leave a message,
145 after that we return the view. If the space is not public we check
146 for the view permission of the object, if the user doesn't have it we
147 return a 403. Since dispatch is run before anything, this checks are
148 made before obtaining the object. If the user doesn't have the
149 permission we return a 403 code, which is handled by
150 django-guardian and returns a template.
152 .. note:: Take in mind that the dispatch method takes **request** as a
153 parameter.
155 space = get_object_or_404(Space, url=kwargs['space_url'])
157 if space.public:
158 if request.user.is_anonymous():
159 messages.info(self.request, _("Hello anonymous user. Remember \
160 that this space is public to view, but you must \
161 <a href=\"/accounts/register\">register</a> or \
162 <a href=\"/accounts/login\">login</a> to participate."))
164 return super(ViewSpaceIndex, self).dispatch(request, *args, **kwargs)
166 if request.user.has_perm('view_space', space):
167 return super(ViewSpaceIndex, self).dispatch(request, *args, **kwargs)
168 else:
169 raise PermissionDenied
171 def get_object(self):
172 # Makes sure the space ins't already in the cache before hitting
173 # the database
174 space_url = self.kwargs['space_url']
175 space_object = get_or_insert_object_in_cache(Space, space_url, url=space_url)
176 return space_object
178 # Get extra context data
179 def get_context_data(self, **kwargs):
180 context = super(ViewSpaceIndex, self).get_context_data(**kwargs)
181 # Makes sure the space ins't already in the cache before hitting the
182 # databass
183 place_url = self.kwargs['space_url']
184 place = get_or_insert_object_in_cache(Space, place_url, url=place_url)
185 '''posts_by_score = Comment.objects.filter(is_public=True) \
186 .values('object_pk').annotate(score=Count('id')).order_by('-score')'''
187 posts_by_score = Comment.objects.filter(is_public=True) \
188 .values('object_pk').annotate(score=Count('id')).order_by('-score')
189 post_ids = [int(obj['object_pk']) for obj in posts_by_score]
190 top_posts = Post.objects.filter(space=place.id).in_bulk(post_ids)
191 # print top_posts.values()[0].title
192 # o_list = Comment.objects.annotate(ocount=Count('object_pk'))
193 comment_list = {}
194 most_commented = []
195 for proposal in Proposal.objects.filter(space=place.id):
196 comment_list[proposal.pk] = Comment.objects.filter(object_pk=proposal.pk).count()
197 for p in dict(sorted(comment_list.items(), key=itemgetter(1))):
198 most_commented.append(Proposal.objects.filter(pk=p))
200 highlighted = {}
201 highlight = []
202 for i in Proposal.objects.filter(space=place.id):
203 highlighted[i.pk] = i.support_votes.count
204 for p in dict(sorted(highlighted.items(), key=itemgetter(1))):
205 highlight.append(Proposal.objects.filter(pk=p))
207 context['entities'] = Entity.objects.filter(space=place.id)
208 context['documents'] = Document.objects.filter(space=place.id)
209 context['proposalsets'] = ProposalSet.objects.filter(space=place.id)
210 context['proposals'] = Proposal.objects.filter(space=place.id) \
211 .order_by('-pub_date')
212 context['publication'] = Post.objects.filter(space=place.id) \
213 .order_by('-pub_date')[:5]
214 context['mostviewed'] = Post.objects.filter(space=place.id) \
215 .order_by('-views')[:5]
216 # context['mostcommented'] = [top_posts.get(id,None) for id in post_ids]
217 context['mostcommented'] = filter(None, map(lambda x: top_posts.get(x, None), post_ids))
218 context['mostcommentedproposal'] = most_commented
219 context['highlightedproposal'] = highlight
221 # context['mostcommented'] = sorted(o_list,
222 # key=lambda k: k['ocount'])[:10]
223 # print sorted(o_list, key=lambda k: k['ocount'])[:10]
224 context['page'] = StaticPage.objects.filter(show_footer=True) \
225 .order_by('-order')
226 context['messages'] = messages.get_messages(self.request)
227 context['debates'] = Debate.objects.filter(space=place.id) \
228 .order_by('-date')
229 context['event'] = Event.objects.filter(space=place.id) \
230 .order_by('-event_date')
231 context['votings'] = Voting.objects.filter(space=place.id)
232 context['polls'] = Poll.objects.filter(space=place.id)
233 # True if the request.user has admin rights on this space
234 context['polls'] = Poll.objects.filter(space=place.id)
235 return context
238 # Please take in mind that the change_space view can't be replaced by a CBV
239 # (class-based view) since it manipulates two forms at the same time. Apparently
240 # that creates some trouble in the django API. See this ticket:
241 # https://code.djangoproject.com/ticket/16256
242 def edit_space(request, space_url):
245 Returns a form filled with the current space data to edit. Access to
246 this view is restricted only to site and space administrators. The filter
247 for space administrators is given by the change_space permission and their
248 belonging to that space.
250 :attributes: - place: current space intance.
251 - form: SpaceForm instance.
252 - form_uncommited: form instance before commiting to the DB,
253 so we can modify the data.
254 :permissions required: spaces.change_space, spaces.admin_space
255 :param space_url: Space URL
256 :rtype: HTML Form
257 :context: form, get_place
259 place = get_object_or_404(Space, url=space_url)
261 if (request.user.has_perm('change_space', place) and
262 request.user.has_perm('admin_space', place)):
263 form = SpaceForm(request.POST or None, request.FILES or None,
264 instance=place)
265 entity_forms = EntityFormSet(request.POST or None, request.FILES
266 or None, queryset=Entity.objects.all().filter(space=place))
268 if request.method == 'POST':
269 if form.is_valid() and entity_forms.is_valid():
270 form_uncommited = form.save(commit=False)
271 form_uncommited.author = request.user
273 new_space = form_uncommited.save()
274 space = get_object_or_404(Space, name=form_uncommited.name)
276 ef_uncommited = entity_forms.save(commit=False)
277 for ef in ef_uncommited:
278 ef.space = space
279 ef.save()
281 form.save_m2m()
282 return HttpResponseRedirect(reverse(urln.SPACE_INDEX,
283 kwargs={'space_url': space.url}))
285 return render_to_response('spaces/space_form.html', {'form': form,
286 'get_place': place, 'entityformset': entity_forms},
287 context_instance=RequestContext(request))
289 else:
290 raise PermissionDenied
293 class DeleteSpace(DeleteView):
296 Returns a confirmation page before deleting the space object completely.
297 This does not delete the space related content. Only the site
298 administrators can delete a space.
300 :attributes: space_url
301 :permissions required: spaces.delete_space, spaces.admin_space
302 :rtype: Confirmation
304 context_object_name = 'get_place'
305 success_url = '/'
307 def dispatch(self, request, *args, **kwargs):
308 space = get_object_or_404(Space, url=kwargs['space_url'])
310 if (request.user.has_perm('delete_space', space) and
311 request.user.has_perm('admin_space', space)):
312 return super(DeleteSpace, self).dispatch(request, *args, **kwargs)
313 else:
314 raise PermissionDenied
316 def get_object(self):
317 space_url = self.kwargs['space_url']
318 space = get_object_or_404(Space, url=space_url)
319 return space
322 class ListSpaces(ListView):
325 Return a list of spaces in the system (except private ones) using a generic
326 view. The users associated to a private spaces will see it, but not the
327 other private spaces. ListSpaces is a django generic :class:`ListView`.
329 .. note:: Permissions on this view are used only to filter the spaces list
330 but the view itself is public.
332 :attributes: space_url
333 :permissions required: spaces.view_space
334 :rtype: Object list
335 :contexts: object_list
337 paginate_by = 10
339 public_spaces = Space.objects.filter(public=True)
340 all_spaces = Space.objects.all()
342 def get_queryset(self):
344 # I think I should explain this mess. What we want to obtain here is:
345 # a list of public spaces in case the user is anonymous, or a list of
346 # the public spaces plus the spaces the user is registered to if the
347 # user is logged in.
348 # To do the second, we create a set of PK objects, and outside of the
349 # 'for' loop we make a queryset for those PK objects, after that we
350 # combine the data of the user spaces and public ones with the '|'
351 # operand.
352 current_user = self.request.user
353 user_spaces = set()
354 all_spaces = Space.objects.all()
355 public_spaces = Spaces.objects.filter(public=True)
357 if not current_user.is_anonymous():
358 for space in self.all_spaces:
359 if current_user.has_perm('view_space', space):
360 user_spaces.add(space.pk)
362 user_spaces = Space.objects.filter(pk__in=user_spaces)
363 return self.public_spaces | user_spaces
365 return self.public_spaces
367 def get_context_data(self, **kwargs):
368 context = super(ListSpaces, self).get_context_data(**kwargs)
369 context['public_spaces'] = self.public_spaces
370 return context
373 def edit_roles(request, space_url):
376 The edit_role function works to provide a way for space administrators to
377 modify the users roles inside a space, at the space level.
379 It basically works as an AJAX communication where the frontend sends to key
380 values: userid and perm, containing the user ID and the permission code,
381 which later we compare with the permissions dictionary. If the user has the
382 permission we go to the next one and so on.
384 There is a special perm code called "delete" that triggers the deletion of
385 all the permissions for the current user on the current space.
387 :ajax keys: userid, perm
388 :returns: HTML
389 :versionadded: 0.1.9
392 space = get_object_or_404(Space, url=space_url)
393 perm_dict = {
394 'admins': ['admin_space', 'view_space'],
395 'mods': ['mod_space', 'view_space'],
396 'users': ['view_space',]
399 if request.user.has_perm('admin_space', space):
400 if request.method == 'POST':
401 user = get_object_or_404(User, pk = request.POST['userid'])
402 cur_user_perms = get_perms(user, space)
404 if request.POST['perm'] == "delete":
405 for p in cur_user_perms:
406 remove_perm(p, user, space)
407 return HttpResponse('Permissions deleted.')
408 else:
409 try:
410 perm = perm_dict[request.POST['perm']]
411 for p in perm:
412 if p in cur_user_perms:
413 pass
414 else:
415 assign_perm(p, user, space)
416 return HttpResponse('Permissions assigned.')
417 except:
418 return HttpResponse('Permission code not valid.')
419 else:
420 space_users = get_users_with_perms(space, with_superusers=False)
421 admins = set()
422 mods = set()
423 users = set()
424 for user in space_users:
425 if user.has_perm('admin_space', space):
426 admins.add(user)
427 elif user.has_perm('mod_space', space):
428 mods.add(user)
429 else:
430 # We omit the check for "view_space" because the space_users
431 # variable is already filtered to show only the users with permissions
432 # on that object and users shows all the users in the space.
433 users.add(user)
435 return render_to_response('spaces/user_groups.html',
436 {'get_place': space, 'user_admins': admins, 'user_mods': mods,
437 'user_users': users}, context_instance=RequestContext(request))
438 else:
439 raise PermissionDenied