Improve influence comment handling.
[matthijs/projects/xerxes.git] / influences / views.py
1 from django import forms
2 from django.contrib.auth.decorators import login_required
3 from django.shortcuts import render_to_response
4 from django.shortcuts import get_object_or_404
5 from django.template import RequestContext
6 from django.utils.translation import ugettext as _
7 from django.contrib.auth.models import User
8 from django.contrib.contenttypes.models import ContentType
9 from django.core.urlresolvers import reverse
10 from django.http import HttpResponseRedirect, HttpResponseForbidden
11 from django.views.generic.list_detail import object_detail, object_list
12 from threadedcomments.models import ThreadedComment
13 from threadedcomments.forms import ThreadedCommentForm
14 from threadedcomments.views import comment
15 from xerxes.influences.models import Character
16 from xerxes.influences.models import Influence
17 from xerxes.tools.forms import ContextModelForm
18
19 #
20 # A few comment form classes, to handle the various cases (staff/no staff,
21 # reply to public/private post)
22
23 # It is probably possible to reduce this mess a bit using metaclasses, but I
24 # didn't get this to work yet.
25
26 class InfluenceCommentForm(ThreadedCommentForm):
27     def __init__(self, *args, **kwargs):
28         super(InfluenceCommentForm, self).__init__(*args, **kwargs)
29
30     class Meta(ThreadedCommentForm.Meta):
31         exclude = ('markup', )
32
33 class AdminInfluenceCommentForm(ThreadedCommentForm):
34     def __init__(self, *args, **kwargs):
35         super(AdminInfluenceCommentForm, self).__init__(*args, **kwargs)
36
37     class Meta(ThreadedCommentForm.Meta):
38         fields = ThreadedCommentForm.Meta.fields + ('is_public',)
39
40 class AdminPrivateInfluenceCommentForm(ThreadedCommentForm):
41     def __init__(self, *args, **kwargs):
42         super(AdminPrivateInfluenceCommentForm, self).__init__(*args, **kwargs)
43         self.instance.is_public = False
44
45 def get_influence_comment_form(is_staff, reply_to):
46     """ Gets the form class that a user can use to reply to the given comment.
47     reply_to can be None, in which case the form class for a new comment is
48     returned. """
49     allow_markup = allow_private = is_staff
50     if reply_to:
51         allow_public = reply_to.is_public
52     else:
53         allow_public = True
54     return _get_influence_comment_form(allow_markup, allow_public, allow_private)
55     
56 def _get_influence_comment_form(allow_markup, allow_public, allow_private):
57     """ Internal wrapper that selects the right form class depending on the
58     given options. Should not be called directly, call
59     get_influence_comment_form instead. """
60     if allow_markup and allow_public and allow_private:
61         return AdminInfluenceCommentForm
62     elif allow_markup and not allow_public and allow_private:
63         return AdminPrivateInfluenceCommentForm
64     elif not allow_markup and allow_public and not allow_private:
65         return InfluenceCommentForm
66     else:
67         raise Exception("Unsupported configuration")
68
69 class InfluenceForm(ContextModelForm):
70     class Meta:
71         model = Influence
72         fields = ('character', 'contact', 'summary', 'description')
73
74 class CharacterForm(ContextModelForm):
75     class Meta:
76         model = Character
77         fields = ('name')
78
79 @login_required
80 def add_influence(request, character_id=None):
81     initial = {}
82     # Get the current user's characters
83     chars = request.user.character_set.all()
84
85     # If a character_id was specified in the url, or there is only one
86     # character, preselect it.
87     if (character_id):
88         initial['character'] = character_id
89     elif (chars.count() == 1):
90         initial['character'] = chars[0].id
91
92
93     f = InfluenceForm(request=request, initial=initial)
94
95     # Only allow characters of the current user. Putting this here also
96     # ensures that a form will not validate when any other choice was
97     # selected (perhaps through URL crafting).
98     f.fields['character']._set_queryset(chars)
99
100     if (f.is_valid()):
101         # The form was submitted, let's save it.
102         influence = f.save()
103         # Redirect to the just saved influence
104         return HttpResponseRedirect(reverse('influences_influence_detail', args=[influence.id]))
105
106     return render_to_response('influences/add_influence.html', {'form' : f}, RequestContext(request))
107
108 @login_required
109 def add_character(request):
110     f = CharacterForm(request=request)
111     if (f.is_valid()):
112         character = f.save(commit=False)
113         character.player = request.user
114         character.save()
115         return HttpResponseRedirect(reverse('influences_character_detail', args=[character.id]))
116
117     return render_to_response('influences/add_character.html', {'form' : f}, RequestContext(request))
118
119 @login_required
120 def index(request):
121     # Only show this player's characters and influences
122     characters = request.user.character_set.all()
123     influences = Influence.objects.filter(character__player=request.user)
124     return render_to_response('influences/index.html', {'characters' : characters, 'influences' : influences}, RequestContext(request))
125
126 #
127 # The views below are very similar to django's generic views (in fact,
128 # they used to be generic views before). However, since they all depend
129 # on the currently logged in user (for limiting the show list or
130 # performing access control), we won't actually use the generic views
131 # here.
132
133 @login_required
134 def character_list(request):
135     # Only show this player's characters
136     os = request.user.character_set.all()
137     return render_to_response('influences/character_list.html', {'object_list' : os}, RequestContext(request))
138
139 @login_required
140 def character_detail(request, object_id):
141     o = Character.objects.get(pk=object_id)
142     # Don't show other player's characters
143     if (o.player != request.user):
144         return HttpResponseForbidden("Forbidden -- Trying to view somebody else's character")
145     return render_to_response('influences/character_detail.html', {'object' : o}, RequestContext(request))
146
147 @login_required
148 def influence_list(request):
149     # Only show this player's influences
150     os = Influence.objects.filter(character__player=request.user)
151     return render_to_response('influences/influence_list.html', {'object_list' : os}, RequestContext(request))
152
153 @login_required
154 def influence_detail(request, object_id):
155
156     def quote_reply(comment):
157         return "\n".join(["> " + l for l in comment.comment.split("\n")])
158
159     o = Influence.objects.get(pk=object_id)
160     # Don't show other player's influences
161     if (o.character.player != request.user):
162         return HttpResponseForbidden("Forbidden -- Trying to view influences of somebody else's character")
163
164     # Show all comments to staff, but only public comments to other
165     # users
166     if request.user.is_staff:
167         comments = ThreadedComment.objects.get_tree(o)
168     else:
169         comments = ThreadedComment.public.get_tree(o)
170
171     # Annotate each comment with a proper reply form
172     for comment in comments:
173         initial = { 'comment' : quote_reply(comment) }
174         comment.reply_form = get_influence_comment_form(request.user.is_staff, comment)(initial=initial)
175     
176     context  = {
177         'object' : o,
178         'comments' : comments,
179         'comment_form' : get_influence_comment_form(request.user.is_staff, None)
180     }
181     return render_to_response('influences/influence_detail.html', context, RequestContext(request))
182
183 @login_required
184 def influence_comment(request, edit_id=None, *args, **kwargs):
185     # Add the content_type, since we don't put in in the url explicitly
186     kwargs['content_type'] = ContentType.objects.get_for_model(Influence).id
187     # Find the comment to which we're replying, so we can get the right form for it.
188     if edit_id:
189         reply_to = get_object_or_404(ThreadedComment, id=edit_id)
190     else:
191         reply_to = None
192     # Find the right form class
193     kwargs['form_class']   = get_influence_comment_form(request.user.is_staff, reply_to)
194     return comment(request, edit_id=edit_id, *args, **kwargs)
195
196 # vim: set sts=4 sw=4 expandtab: