]> git.openstreetmap.org Git - osqa.git/blob - forum/views/writers.py
Initial commit
[osqa.git] / forum / views / writers.py
1 # encoding:utf-8
2 import os.path
3 import time, datetime, random
4 import logging
5 from django.core.files.storage import default_storage
6 from django.shortcuts import render_to_response, get_object_or_404
7 from django.contrib.auth.decorators import login_required
8 from django.http import HttpResponseRedirect, HttpResponse, HttpResponseForbidden, Http404
9 from django.template import RequestContext
10 from django.utils.html import *
11 from django.utils import simplejson
12 from django.utils.translation import ugettext as _
13 from django.core.urlresolvers import reverse
14 from django.core.exceptions import PermissionDenied
15
16 from forum.utils.html import sanitize_html
17 from markdown2 import Markdown
18 from forum.forms import *
19 from forum.models import *
20 from forum.auth import *
21 from forum.const import *
22 from forum import auth
23 from forum.utils.forms import get_next_url
24 from forum.views.readers import _get_tags_cache_json
25
26 # used in index page
27 INDEX_PAGE_SIZE = 20
28 INDEX_AWARD_SIZE = 15
29 INDEX_TAGS_SIZE = 100
30 # used in tags list
31 DEFAULT_PAGE_SIZE = 60
32 # used in questions
33 QUESTIONS_PAGE_SIZE = 10
34 # used in answers
35 ANSWERS_PAGE_SIZE = 10
36
37 markdowner = Markdown(html4tags=True)
38
39 def upload(request):#ajax upload file to a question or answer 
40     class FileTypeNotAllow(Exception):
41         pass
42     class FileSizeNotAllow(Exception):
43         pass
44     class UploadPermissionNotAuthorized(Exception):
45         pass
46
47     #<result><msg><![CDATA[%s]]></msg><error><![CDATA[%s]]></error><file_url>%s</file_url></result>
48     xml_template = "<result><msg><![CDATA[%s]]></msg><error><![CDATA[%s]]></error><file_url>%s</file_url></result>"
49
50     try:
51         f = request.FILES['file-upload']
52         # check upload permission
53         if not auth.can_upload_files(request.user):
54             raise UploadPermissionNotAuthorized
55
56         # check file type
57         file_name_suffix = os.path.splitext(f.name)[1].lower()
58         if not file_name_suffix in settings.ALLOW_FILE_TYPES:
59             raise FileTypeNotAllow
60
61         # generate new file name
62         new_file_name = str(time.time()).replace('.', str(random.randint(0,100000))) + file_name_suffix
63         # use default storage to store file
64         default_storage.save(new_file_name, f)
65         # check file size
66         # byte
67         size = default_storage.size(new_file_name)
68         if size > settings.ALLOW_MAX_FILE_SIZE:
69             default_storage.delete(new_file_name)
70             raise FileSizeNotAllow
71
72         result = xml_template % ('Good', '', default_storage.url(new_file_name))
73     except UploadPermissionNotAuthorized:
74         result = xml_template % ('', _('uploading images is limited to users with >60 reputation points'), '')
75     except FileTypeNotAllow:
76         result = xml_template % ('', _("allowed file types are 'jpg', 'jpeg', 'gif', 'bmp', 'png', 'tiff'"), '')
77     except FileSizeNotAllow:
78         result = xml_template % ('', _("maximum upload file size is %sK") % settings.ALLOW_MAX_FILE_SIZE / 1024, '')
79     except Exception:
80         result = xml_template % ('', _('Error uploading file. Please contact the site administrator. Thank you. %s' % Exception), '')
81
82     return HttpResponse(result, mimetype="application/xml")
83
84 #@login_required #actually you can post anonymously, but then must register
85 def ask(request):#view used to ask a new question
86     """a view to ask a new question
87     gives space for q title, body, tags and checkbox for to post as wiki
88
89     user can start posting a question anonymously but then
90     must login/register in order for the question go be shown
91     """
92     if request.method == "POST":
93         form = AskForm(request.POST)
94         if form.is_valid():
95
96             added_at = datetime.datetime.now()
97             title = strip_tags(form.cleaned_data['title'].strip())
98             wiki = form.cleaned_data['wiki']
99             tagnames = form.cleaned_data['tags'].strip()
100             text = form.cleaned_data['text']
101             html = sanitize_html(markdowner.convert(text))
102             summary = strip_tags(html)[:120]
103
104             if request.user.is_authenticated():
105                 author = request.user 
106
107                 question = Question.objects.create_new(
108                     title            = title,
109                     author           = author, 
110                     added_at         = added_at,
111                     wiki             = wiki,
112                     tagnames         = tagnames,
113                     summary          = summary,
114                     text = sanitize_html(markdowner.convert(text))
115                 )
116
117                 return HttpResponseRedirect(question.get_absolute_url())
118             else:
119                 request.session.flush()
120                 session_key = request.session.session_key
121                 question = AnonymousQuestion(
122                     session_key = session_key,
123                     title       = title,
124                     tagnames = tagnames,
125                     wiki = wiki,
126                     text = text,
127                     summary = summary,
128                     added_at = added_at,
129                     ip_addr = request.META['REMOTE_ADDR'],
130                 )
131                 question.save()
132                 return HttpResponseRedirect(reverse('auth_action_signin', kwargs={'action': 'newquestion'}))
133     else:
134         form = AskForm()
135
136     tags = _get_tags_cache_json()
137     return render_to_response('ask.html', {
138         'form' : form,
139         'tags' : tags,
140         'email_validation_faq_url':reverse('faq') + '#validate',
141         }, context_instance=RequestContext(request))
142
143 @login_required
144 def edit_question(request, id):#edit or retag a question
145     """view to edit question
146     """
147     question = get_object_or_404(Question, id=id)
148     if question.deleted and not auth.can_view_deleted_post(request.user, question):
149         raise Http404
150     if auth.can_edit_post(request.user, question):
151         return _edit_question(request, question)
152     elif auth.can_retag_questions(request.user):
153         return _retag_question(request, question)
154     else:
155         raise Http404
156
157 def _retag_question(request, question):#non-url subview of edit question - just retag
158     """retag question sub-view used by
159     view "edit_question"
160     """
161     if request.method == 'POST':
162         form = RetagQuestionForm(question, request.POST)
163         if form.is_valid():
164             if form.has_changed():
165                 latest_revision = question.get_latest_revision()
166                 retagged_at = datetime.datetime.now()
167                 # Update the Question itself
168                 Question.objects.filter(id=question.id).update(
169                     tagnames         = form.cleaned_data['tags'],
170                     last_edited_at   = retagged_at,
171                     last_edited_by   = request.user,
172                     last_activity_at = retagged_at,
173                     last_activity_by = request.user
174                 )
175                 # Update the Question's tag associations
176                 tags_updated = Question.objects.update_tags(question,
177                     form.cleaned_data['tags'], request.user)
178                 # Create a new revision
179                 QuestionRevision.objects.create(
180                     question   = question,
181                     title      = latest_revision.title,
182                     author     = request.user,
183                     revised_at = retagged_at,
184                     tagnames   = form.cleaned_data['tags'],
185                     summary    = CONST['retagged'],
186                     text       = latest_revision.text
187                 )
188                 # send tags updated singal
189                 tags_updated.send(sender=question.__class__, question=question)
190
191             return HttpResponseRedirect(question.get_absolute_url())
192     else:
193         form = RetagQuestionForm(question)
194     return render_to_response('question_retag.html', {
195         'question': question,
196         'form' : form,
197         'tags' : _get_tags_cache_json(),
198     }, context_instance=RequestContext(request))
199
200 def _edit_question(request, question):#non-url subview of edit_question - just edit the body/title
201     latest_revision = question.get_latest_revision()
202     revision_form = None
203     if request.method == 'POST':
204         if 'select_revision' in request.POST:
205             # user has changed revistion number
206             revision_form = RevisionForm(question, latest_revision, request.POST)
207             if revision_form.is_valid():
208                 # Replace with those from the selected revision
209                 form = EditQuestionForm(question,
210                     QuestionRevision.objects.get(question=question,
211                         revision=revision_form.cleaned_data['revision']))
212             else:
213                 form = EditQuestionForm(question, latest_revision, request.POST)
214         else:
215             # Always check modifications against the latest revision
216             form = EditQuestionForm(question, latest_revision, request.POST)
217             if form.is_valid():
218                 html = sanitize_html(markdowner.convert(form.cleaned_data['text']))
219                 if form.has_changed():
220                     edited_at = datetime.datetime.now()
221                     tags_changed = (latest_revision.tagnames !=
222                                     form.cleaned_data['tags'])
223                     tags_updated = False
224                     # Update the Question itself
225                     updated_fields = {
226                         'title': form.cleaned_data['title'],
227                         'last_edited_at': edited_at,
228                         'last_edited_by': request.user,
229                         'last_activity_at': edited_at,
230                         'last_activity_by': request.user,
231                         'tagnames': form.cleaned_data['tags'],
232                         'summary': strip_tags(html)[:120],
233                         'html': html,
234                     }
235
236                     # only save when it's checked
237                     # because wiki doesn't allow to be edited if last version has been enabled already
238                     # and we make sure this in forms.
239                     if ('wiki' in form.cleaned_data and
240                         form.cleaned_data['wiki']):
241                         updated_fields['wiki'] = True
242                         updated_fields['wikified_at'] = edited_at
243
244                     Question.objects.filter(
245                         id=question.id).update(**updated_fields)
246                     # Update the Question's tag associations
247                     if tags_changed:
248                         tags_updated = Question.objects.update_tags(
249                             question, form.cleaned_data['tags'], request.user)
250                     # Create a new revision
251                     revision = QuestionRevision(
252                         question   = question,
253                         title      = form.cleaned_data['title'],
254                         author     = request.user,
255                         revised_at = edited_at,
256                         tagnames   = form.cleaned_data['tags'],
257                         text       = form.cleaned_data['text'],
258                     )
259                     if form.cleaned_data['summary']:
260                         revision.summary = form.cleaned_data['summary']
261                     else:
262                         revision.summary = 'No.%s Revision' % latest_revision.revision
263                     revision.save()
264
265                 return HttpResponseRedirect(question.get_absolute_url())
266     else:
267
268         revision_form = RevisionForm(question, latest_revision)
269         form = EditQuestionForm(question, latest_revision)
270     return render_to_response('question_edit.html', {
271         'question': question,
272         'revision_form': revision_form,
273         'form' : form,
274         'tags' : _get_tags_cache_json()
275     }, context_instance=RequestContext(request))
276
277 @login_required
278 def edit_answer(request, id):
279     answer = get_object_or_404(Answer, id=id)
280     if answer.deleted and not auth.can_view_deleted_post(request.user, answer):
281         raise Http404
282     elif not auth.can_edit_post(request.user, answer):
283         raise Http404
284     else:
285         latest_revision = answer.get_latest_revision()
286         if request.method == "POST":
287             if 'select_revision' in request.POST:
288                 # user has changed revistion number
289                 revision_form = RevisionForm(answer, latest_revision, request.POST)
290                 if revision_form.is_valid():
291                     # Replace with those from the selected revision
292                     form = EditAnswerForm(answer,
293                                           AnswerRevision.objects.get(answer=answer,
294                                           revision=revision_form.cleaned_data['revision']))
295                 else:
296                     form = EditAnswerForm(answer, latest_revision, request.POST)
297             else:
298                 form = EditAnswerForm(answer, latest_revision, request.POST)
299                 if form.is_valid():
300                     html = sanitize_html(markdowner.convert(form.cleaned_data['text']))
301                     if form.has_changed():
302                         edited_at = datetime.datetime.now()
303                         updated_fields = {
304                             'last_edited_at': edited_at,
305                             'last_edited_by': request.user,
306                             'html': html,
307                         }
308                         Answer.objects.filter(id=answer.id).update(**updated_fields)
309
310                         revision = AnswerRevision(
311                                                   answer=answer,
312                                                   author=request.user,
313                                                   revised_at=edited_at,
314                                                   text=form.cleaned_data['text']
315                                                   )
316
317                         if form.cleaned_data['summary']:
318                             revision.summary = form.cleaned_data['summary']
319                         else:
320                             revision.summary = 'No.%s Revision' % latest_revision.revision
321                         revision.save()
322
323                         answer.question.last_activity_at = edited_at
324                         answer.question.last_activity_by = request.user
325                         answer.question.save()
326
327                     return HttpResponseRedirect(answer.get_absolute_url())
328         else:
329             revision_form = RevisionForm(answer, latest_revision)
330             form = EditAnswerForm(answer, latest_revision)
331         return render_to_response('answer_edit.html', {
332                                   'answer': answer,
333                                   'revision_form': revision_form,
334                                   'form': form,
335                                   }, context_instance=RequestContext(request))
336
337 def answer(request, id):#process a new answer
338     question = get_object_or_404(Question, id=id)
339     if request.method == "POST":
340         form = AnswerForm(question, request.user, request.POST)
341         if form.is_valid():
342             wiki = form.cleaned_data['wiki']
343             text = form.cleaned_data['text']
344             update_time = datetime.datetime.now()
345
346             if request.user.is_authenticated():
347                 Answer.objects.create_new(
348                                   question=question,
349                                   author=request.user,
350                                   added_at=update_time,
351                                   wiki=wiki,
352                                   text=sanitize_html(markdowner.convert(text)),
353                                   email_notify=form.cleaned_data['email_notify']
354                                   )
355             else:
356                 request.session.flush()
357                 html = sanitize_html(markdowner.convert(text))
358                 summary = strip_tags(html)[:120]
359                 anon = AnonymousAnswer(
360                                        question=question,
361                                        wiki=wiki,
362                                        text=text,
363                                        summary=summary,
364                                        session_key=request.session.session_key,
365                                        ip_addr=request.META['REMOTE_ADDR'],
366                                        )
367                 anon.save()
368                 return HttpResponseRedirect(reverse('auth_action_signin', kwargs={'action': 'newanswer'}))
369
370     return HttpResponseRedirect(question.get_absolute_url())
371
372 def __generate_comments_json(obj, type, user):#non-view generates json data for the post comments
373     comments = obj.comments.all().order_by('id')
374     # {"Id":6,"PostId":38589,"CreationDate":"an hour ago","Text":"hello there!","UserDisplayName":"Jarrod Dixon","UserUrl":"/users/3/jarrod-dixon","DeleteUrl":null}
375     json_comments = []
376     from forum.templatetags.extra_tags import diff_date
377     for comment in comments:
378         comment_user = comment.user
379         delete_url = ""
380         if user != None and auth.can_delete_comment(user, comment):
381             #/posts/392845/comments/219852/delete
382             #todo translate this url
383             delete_url = reverse('index') + type + "s/%s/comments/%s/delete/" % (obj.id, comment.id)
384         json_comments.append({"id" : comment.id,
385             "object_id" : obj.id,
386             "comment_age" : diff_date(comment.added_at),
387             "text" : comment.comment,
388             "user_display_name" : comment_user.username,
389             "user_url" : comment_user.get_profile_url(),
390             "delete_url" : delete_url
391         })
392
393     data = simplejson.dumps(json_comments)
394     return HttpResponse(data, mimetype="application/json")
395
396
397 def question_comments(request, id):#ajax handler for loading comments to question
398     question = get_object_or_404(Question, id=id)
399     user = request.user
400     return __comments(request, question, 'question')
401
402 def answer_comments(request, id):#ajax handler for loading comments on answer
403     answer = get_object_or_404(Answer, id=id)
404     user = request.user
405     return __comments(request, answer, 'answer')
406
407 def __comments(request, obj, type):#non-view generic ajax handler to load comments to an object
408     # only support get post comments by ajax now
409     user = request.user
410     if request.is_ajax():
411         if request.method == "GET":
412             response = __generate_comments_json(obj, type, user)
413         elif request.method == "POST":
414             if auth.can_add_comments(user,obj):
415                 comment_data = request.POST.get('comment')
416                 comment = Comment(content_object=obj, comment=comment_data, user=request.user)
417                 comment.save()
418                 obj.comment_count = obj.comment_count + 1
419                 obj.save()
420                 response = __generate_comments_json(obj, type, user)
421             else:
422                 response = HttpResponseForbidden(mimetype="application/json")
423         return response
424
425 def delete_comment(request, object_id='', comment_id='', commented_object_type=None):#ajax handler to delete comment
426     response = None
427     commented_object = None
428     if commented_object_type == 'question':
429         commented_object = Question
430     elif commented_object_type == 'answer':
431         commented_object = Answer
432
433     if request.is_ajax():
434         comment = get_object_or_404(Comment, id=comment_id)
435         if auth.can_delete_comment(request.user, comment):
436             obj = get_object_or_404(commented_object, id=object_id)
437             obj.comments.remove(comment)
438             obj.comment_count = obj.comment_count - 1
439             obj.save()
440             user = request.user
441             return __generate_comments_json(obj, commented_object_type, user)
442     raise PermissionDenied()