3 import time, datetime, random
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
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
31 DEFAULT_PAGE_SIZE = 60
33 QUESTIONS_PAGE_SIZE = 10
35 ANSWERS_PAGE_SIZE = 10
37 markdowner = Markdown(html4tags=True)
39 def upload(request):#ajax upload file to a question or answer
40 class FileTypeNotAllow(Exception):
42 class FileSizeNotAllow(Exception):
44 class UploadPermissionNotAuthorized(Exception):
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>"
51 f = request.FILES['file-upload']
52 # check upload permission
53 if not auth.can_upload_files(request.user):
54 raise UploadPermissionNotAuthorized
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
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)
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
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, '')
80 result = xml_template % ('', _('Error uploading file. Please contact the site administrator. Thank you. %s' % Exception), '')
82 return HttpResponse(result, mimetype="application/xml")
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
89 user can start posting a question anonymously but then
90 must login/register in order for the question go be shown
92 if request.method == "POST":
93 form = AskForm(request.POST)
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]
104 if request.user.is_authenticated():
105 author = request.user
107 question = Question.objects.create_new(
114 text = sanitize_html(markdowner.convert(text))
117 return HttpResponseRedirect(question.get_absolute_url())
119 request.session.flush()
120 session_key = request.session.session_key
121 question = AnonymousQuestion(
122 session_key = session_key,
129 ip_addr = request.META['REMOTE_ADDR'],
132 return HttpResponseRedirect(reverse('auth_action_signin', kwargs={'action': 'newquestion'}))
136 tags = _get_tags_cache_json()
137 return render_to_response('ask.html', {
140 'email_validation_faq_url':reverse('faq') + '#validate',
141 }, context_instance=RequestContext(request))
144 def edit_question(request, id):#edit or retag a question
145 """view to edit question
147 question = get_object_or_404(Question, id=id)
148 if question.deleted and not auth.can_view_deleted_post(request.user, question):
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)
157 def _retag_question(request, question):#non-url subview of edit question - just retag
158 """retag question sub-view used by
161 if request.method == 'POST':
162 form = RetagQuestionForm(question, request.POST)
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
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(
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
188 # send tags updated singal
189 tags_updated.send(sender=question.__class__, question=question)
191 return HttpResponseRedirect(question.get_absolute_url())
193 form = RetagQuestionForm(question)
194 return render_to_response('question_retag.html', {
195 'question': question,
197 'tags' : _get_tags_cache_json(),
198 }, context_instance=RequestContext(request))
200 def _edit_question(request, question):#non-url subview of edit_question - just edit the body/title
201 latest_revision = question.get_latest_revision()
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']))
213 form = EditQuestionForm(question, latest_revision, request.POST)
215 # Always check modifications against the latest revision
216 form = EditQuestionForm(question, latest_revision, request.POST)
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'])
224 # Update the Question itself
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],
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
244 Question.objects.filter(
245 id=question.id).update(**updated_fields)
246 # Update the Question's tag associations
248 tags_updated = Question.objects.update_tags(
249 question, form.cleaned_data['tags'], request.user)
250 # Create a new revision
251 revision = QuestionRevision(
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'],
259 if form.cleaned_data['summary']:
260 revision.summary = form.cleaned_data['summary']
262 revision.summary = 'No.%s Revision' % latest_revision.revision
265 return HttpResponseRedirect(question.get_absolute_url())
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,
274 'tags' : _get_tags_cache_json()
275 }, context_instance=RequestContext(request))
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):
282 elif not auth.can_edit_post(request.user, answer):
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']))
296 form = EditAnswerForm(answer, latest_revision, request.POST)
298 form = EditAnswerForm(answer, latest_revision, request.POST)
300 html = sanitize_html(markdowner.convert(form.cleaned_data['text']))
301 if form.has_changed():
302 edited_at = datetime.datetime.now()
304 'last_edited_at': edited_at,
305 'last_edited_by': request.user,
308 Answer.objects.filter(id=answer.id).update(**updated_fields)
310 revision = AnswerRevision(
313 revised_at=edited_at,
314 text=form.cleaned_data['text']
317 if form.cleaned_data['summary']:
318 revision.summary = form.cleaned_data['summary']
320 revision.summary = 'No.%s Revision' % latest_revision.revision
323 answer.question.last_activity_at = edited_at
324 answer.question.last_activity_by = request.user
325 answer.question.save()
327 return HttpResponseRedirect(answer.get_absolute_url())
329 revision_form = RevisionForm(answer, latest_revision)
330 form = EditAnswerForm(answer, latest_revision)
331 return render_to_response('answer_edit.html', {
333 'revision_form': revision_form,
335 }, context_instance=RequestContext(request))
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)
342 wiki = form.cleaned_data['wiki']
343 text = form.cleaned_data['text']
344 update_time = datetime.datetime.now()
346 if request.user.is_authenticated():
347 Answer.objects.create_new(
350 added_at=update_time,
352 text=sanitize_html(markdowner.convert(text)),
353 email_notify=form.cleaned_data['email_notify']
356 request.session.flush()
357 html = sanitize_html(markdowner.convert(text))
358 summary = strip_tags(html)[:120]
359 anon = AnonymousAnswer(
364 session_key=request.session.session_key,
365 ip_addr=request.META['REMOTE_ADDR'],
368 return HttpResponseRedirect(reverse('auth_action_signin', kwargs={'action': 'newanswer'}))
370 return HttpResponseRedirect(question.get_absolute_url())
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}
376 from forum.templatetags.extra_tags import diff_date
377 for comment in comments:
378 comment_user = comment.user
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
393 data = simplejson.dumps(json_comments)
394 return HttpResponse(data, mimetype="application/json")
397 def question_comments(request, id):#ajax handler for loading comments to question
398 question = get_object_or_404(Question, id=id)
400 return __comments(request, question, 'question')
402 def answer_comments(request, id):#ajax handler for loading comments on answer
403 answer = get_object_or_404(Answer, id=id)
405 return __comments(request, answer, 'answer')
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
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)
418 obj.comment_count = obj.comment_count + 1
420 response = __generate_comments_json(obj, type, user)
422 response = HttpResponseForbidden(mimetype="application/json")
425 def delete_comment(request, object_id='', comment_id='', commented_object_type=None):#ajax handler to delete comment
427 commented_object = None
428 if commented_object_type == 'question':
429 commented_object = Question
430 elif commented_object_type == 'answer':
431 commented_object = Answer
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
441 return __generate_comments_json(obj, commented_object_type, user)
442 raise PermissionDenied()