##// END OF EJS Templates
Moved delete view to class-based views
neko259 -
r552:39f0b88d 1.7-dev
parent child Browse files
Show More
@@ -0,0 +1,25 b''
1 from django.shortcuts import redirect, get_object_or_404
2 from django.db import transaction
3
4 from boards.views.base import BaseBoardView
5 from boards.views.redirect_next_mixin import RedirectNextMixin
6 from boards.models import Post
7
8 class DeletePostView(BaseBoardView, RedirectNextMixin):
9
10 @transaction.atomic
11 def get(self, request, post_id):
12 user = self._get_user(request)
13 post = get_object_or_404(Post, id=post_id)
14
15 opening_post = post.is_opening()
16
17 if user.is_moderator():
18 # TODO Show confirmation page before deletion
19 Post.objects.delete_post(post)
20
21 if not opening_post:
22 thread = post.thread_new
23 return redirect('thread', post_id=thread.get_opening_post().id)
24 else:
25 return self.redirect_to_next(request)
@@ -0,0 +1,17 b''
1 from django.shortcuts import redirect
2 from django.http import HttpResponseRedirect
3
4 class RedirectNextMixin:
5
6 def redirect_to_next(self, request):
7 """
8 If a 'next' parameter was specified, redirect to the next page. This
9 is used when the user is required to return to some page after the
10 current view has finished its work.
11 """
12
13 if 'next' in request.GET:
14 next_page = request.GET['next']
15 return HttpResponseRedirect(next_page)
16 else:
17 return redirect('index')
@@ -1,395 +1,393 b''
1 1 from datetime import datetime, timedelta
2 2 from datetime import time as dtime
3 3 import os
4 4 from random import random
5 5 import time
6 6 import math
7 7 import re
8 8 import hashlib
9 9
10 10 from django.core.cache import cache
11 11 from django.core.paginator import Paginator
12 12
13 13 from django.db import models
14 14 from django.http import Http404
15 15 from django.utils import timezone
16 16 from markupfield.fields import MarkupField
17 17
18 18 from neboard import settings
19 19 from boards import thumbs
20 20
21 21 MAX_TITLE_LENGTH = 50
22 22
23 23 APP_LABEL_BOARDS = 'boards'
24 24
25 25 CACHE_KEY_PPD = 'ppd'
26 26
27 27 POSTS_PER_DAY_RANGE = range(7)
28 28
29 29 BAN_REASON_AUTO = 'Auto'
30 30
31 31 IMAGE_THUMB_SIZE = (200, 150)
32 32
33 33 TITLE_MAX_LENGTH = 50
34 34
35 35 DEFAULT_MARKUP_TYPE = 'markdown'
36 36
37 37 NO_PARENT = -1
38 38 NO_IP = '0.0.0.0'
39 39 UNKNOWN_UA = ''
40 40 ALL_PAGES = -1
41 41 IMAGES_DIRECTORY = 'images/'
42 42 FILE_EXTENSION_DELIMITER = '.'
43 43
44 44 SETTING_MODERATE = "moderate"
45 45
46 46 REGEX_REPLY = re.compile('>>(\d+)')
47 47
48 48
49 49 class PostManager(models.Manager):
50 50
51 51 def create_post(self, title, text, image=None, thread=None,
52 52 ip=NO_IP, tags=None, user=None):
53 53 """
54 54 Create new post
55 55 """
56 56
57 57 posting_time = timezone.now()
58 58 if not thread:
59 59 thread = Thread.objects.create(bump_time=posting_time,
60 60 last_edit_time=posting_time)
61 61 else:
62 62 thread.bump()
63 63 thread.last_edit_time = posting_time
64 64 thread.save()
65 65
66 66 post = self.create(title=title,
67 67 text=text,
68 68 pub_time=posting_time,
69 69 thread_new=thread,
70 70 image=image,
71 71 poster_ip=ip,
72 72 poster_user_agent=UNKNOWN_UA, # TODO Get UA at
73 73 # last!
74 74 last_edit_time=posting_time,
75 75 user=user)
76 76
77 77 thread.replies.add(post)
78 78 if tags:
79 79 linked_tags = []
80 80 for tag in tags:
81 81 tag_linked_tags = tag.get_linked_tags()
82 82 if len(tag_linked_tags) > 0:
83 83 linked_tags.extend(tag_linked_tags)
84 84
85 85 tags.extend(linked_tags)
86 86 map(thread.add_tag, tags)
87 87
88 88 self._delete_old_threads()
89 89 self.connect_replies(post)
90 90
91 91 return post
92 92
93 93 def delete_post(self, post):
94 94 """
95 95 Delete post and update or delete its thread
96 96 """
97
97
98 98 thread = post.thread_new
99 99
100 if thread.get_opening_post() == self:
101 thread.replies.delete()
102
103 thread.delete()
100 if post.is_opening():
101 thread.delete_with_posts()
104 102 else:
105 103 thread.last_edit_time = timezone.now()
106 104 thread.save()
107 105
108 106 post.delete()
109 107
110 108 def delete_posts_by_ip(self, ip):
111 109 """
112 110 Delete all posts of the author with same IP
113 111 """
114 112
115 113 posts = self.filter(poster_ip=ip)
116 114 map(self.delete_post, posts)
117 115
118 116 # TODO This method may not be needed any more, because django's paginator
119 117 # is used
120 118 def get_threads(self, tag=None, page=ALL_PAGES,
121 119 order_by='-bump_time', archived=False):
122 120 if tag:
123 121 threads = tag.threads
124 122
125 123 if not threads.exists():
126 124 raise Http404
127 125 else:
128 126 threads = Thread.objects.all()
129 127
130 128 threads = threads.filter(archived=archived).order_by(order_by)
131 129
132 130 if page != ALL_PAGES:
133 131 threads = Paginator(threads, settings.THREADS_PER_PAGE).page(
134 132 page).object_list
135 133
136 134 return threads
137 135
138 136 # TODO Move this method to thread manager
139 137 def _delete_old_threads(self):
140 138 """
141 139 Preserves maximum thread count. If there are too many threads,
142 140 archive the old ones.
143 141 """
144 142
145 143 threads = self.get_threads()
146 144 thread_count = threads.count()
147 145
148 146 if thread_count > settings.MAX_THREAD_COUNT:
149 147 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
150 148 old_threads = threads[thread_count - num_threads_to_delete:]
151 149
152 150 for thread in old_threads:
153 151 thread.archived = True
154 152 thread.last_edit_time = timezone.now()
155 153 thread.save()
156 154
157 155 def connect_replies(self, post):
158 156 """
159 157 Connect replies to a post to show them as a reflink map
160 158 """
161 159
162 160 for reply_number in re.finditer(REGEX_REPLY, post.text.raw):
163 161 post_id = reply_number.group(1)
164 162 ref_post = self.filter(id=post_id)
165 163 if ref_post.count() > 0:
166 164 referenced_post = ref_post[0]
167 165 referenced_post.referenced_posts.add(post)
168 166 referenced_post.last_edit_time = post.pub_time
169 167 referenced_post.save()
170 168
171 169 referenced_thread = referenced_post.thread_new
172 170 referenced_thread.last_edit_time = post.pub_time
173 171 referenced_thread.save()
174 172
175 173 def get_posts_per_day(self):
176 174 """
177 175 Get average count of posts per day for the last 7 days
178 176 """
179 177
180 178 today = datetime.now().date()
181 179 ppd = cache.get(CACHE_KEY_PPD + str(today))
182 180 if ppd:
183 181 return ppd
184 182
185 183 posts_per_days = []
186 184 for i in POSTS_PER_DAY_RANGE:
187 185 day_end = today - timedelta(i + 1)
188 186 day_start = today - timedelta(i + 2)
189 187
190 188 day_time_start = timezone.make_aware(datetime.combine(day_start,
191 189 dtime()), timezone.get_current_timezone())
192 190 day_time_end = timezone.make_aware(datetime.combine(day_end,
193 191 dtime()), timezone.get_current_timezone())
194 192
195 193 posts_per_days.append(float(self.filter(
196 194 pub_time__lte=day_time_end,
197 195 pub_time__gte=day_time_start).count()))
198 196
199 197 ppd = (sum(posts_per_day for posts_per_day in posts_per_days) /
200 198 len(posts_per_days))
201 199 cache.set(CACHE_KEY_PPD, ppd)
202 200 return ppd
203 201
204 202
205 203 class Post(models.Model):
206 204 """A post is a message."""
207 205
208 206 objects = PostManager()
209 207
210 208 class Meta:
211 209 app_label = APP_LABEL_BOARDS
212 210
213 211 # TODO Save original file name to some field
214 212 def _update_image_filename(self, filename):
215 213 """Get unique image filename"""
216 214
217 215 path = IMAGES_DIRECTORY
218 216 new_name = str(int(time.mktime(time.gmtime())))
219 217 new_name += str(int(random() * 1000))
220 218 new_name += FILE_EXTENSION_DELIMITER
221 219 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
222 220
223 221 return os.path.join(path, new_name)
224 222
225 223 title = models.CharField(max_length=TITLE_MAX_LENGTH)
226 224 pub_time = models.DateTimeField()
227 225 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
228 226 escape_html=False)
229 227
230 228 image_width = models.IntegerField(default=0)
231 229 image_height = models.IntegerField(default=0)
232 230
233 231 image_pre_width = models.IntegerField(default=0)
234 232 image_pre_height = models.IntegerField(default=0)
235 233
236 234 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
237 235 blank=True, sizes=(IMAGE_THUMB_SIZE,),
238 236 width_field='image_width',
239 237 height_field='image_height',
240 238 preview_width_field='image_pre_width',
241 239 preview_height_field='image_pre_height')
242 240 image_hash = models.CharField(max_length=36)
243 241
244 242 poster_ip = models.GenericIPAddressField()
245 243 poster_user_agent = models.TextField()
246 244
247 245 thread = models.ForeignKey('Post', null=True, default=None)
248 246 thread_new = models.ForeignKey('Thread', null=True, default=None)
249 247 last_edit_time = models.DateTimeField()
250 248 user = models.ForeignKey('User', null=True, default=None)
251 249
252 250 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
253 251 null=True,
254 252 blank=True, related_name='rfp+')
255 253
256 254 def __unicode__(self):
257 255 return '#' + str(self.id) + ' ' + self.title + ' (' + \
258 256 self.text.raw[:50] + ')'
259 257
260 258 def get_title(self):
261 259 title = self.title
262 260 if len(title) == 0:
263 261 title = self.text.rendered
264 262
265 263 return title
266 264
267 265 def get_sorted_referenced_posts(self):
268 266 return self.referenced_posts.order_by('id')
269 267
270 268 def is_referenced(self):
271 269 return self.referenced_posts.all().exists()
272 270
273 271 def is_opening(self):
274 272 return self.thread_new.get_replies()[0] == self
275 273
276 274 def save(self, *args, **kwargs):
277 275 """
278 276 Save the model and compute the image hash
279 277 """
280 278
281 279 if not self.pk and self.image:
282 280 md5 = hashlib.md5()
283 281 for chunk in self.image.chunks():
284 282 md5.update(chunk)
285 283 self.image_hash = md5.hexdigest()
286 284 super(Post, self).save(*args, **kwargs)
287 285
288 286
289 287 class Thread(models.Model):
290 288
291 289 class Meta:
292 290 app_label = APP_LABEL_BOARDS
293 291
294 292 tags = models.ManyToManyField('Tag')
295 293 bump_time = models.DateTimeField()
296 294 last_edit_time = models.DateTimeField()
297 295 replies = models.ManyToManyField('Post', symmetrical=False, null=True,
298 296 blank=True, related_name='tre+')
299 297 archived = models.BooleanField(default=False)
300 298
301 299 def get_tags(self):
302 300 """
303 301 Get a sorted tag list
304 302 """
305 303
306 304 return self.tags.order_by('name')
307 305
308 306 def bump(self):
309 307 """
310 308 Bump (move to up) thread
311 309 """
312 310
313 311 if self.can_bump():
314 312 self.bump_time = timezone.now()
315 313
316 314 def get_reply_count(self):
317 315 return self.replies.count()
318 316
319 317 def get_images_count(self):
320 318 return self.replies.filter(image_width__gt=0).count()
321 319
322 320 def can_bump(self):
323 321 """
324 322 Check if the thread can be bumped by replying
325 323 """
326 324
327 325 if self.archived:
328 326 return False
329 327
330 328 post_count = self.get_reply_count()
331 329
332 330 return post_count < settings.MAX_POSTS_PER_THREAD
333 331
334 332 def delete_with_posts(self):
335 333 """
336 334 Completely delete thread and all its posts
337 335 """
338 336
339 337 if self.replies.count() > 0:
340 338 self.replies.all().delete()
341 339
342 340 self.delete()
343 341
344 342 def get_last_replies(self):
345 343 """
346 344 Get last replies, not including opening post
347 345 """
348 346
349 347 if settings.LAST_REPLIES_COUNT > 0:
350 348 reply_count = self.get_reply_count()
351 349
352 350 if reply_count > 0:
353 351 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
354 352 reply_count - 1)
355 353 last_replies = self.replies.all().order_by('pub_time')[
356 354 reply_count - reply_count_to_show:]
357 355
358 356 return last_replies
359 357
360 358 def get_skipped_replies_count(self):
361 359 last_replies = self.get_last_replies()
362 360 return self.get_reply_count() - len(last_replies) - 1
363 361
364 362 def get_replies(self):
365 363 """
366 364 Get sorted thread posts
367 365 """
368 366
369 367 return self.replies.all().order_by('pub_time')
370 368
371 369 def add_tag(self, tag):
372 370 """
373 371 Connect thread to a tag and tag to a thread
374 372 """
375 373
376 374 self.tags.add(tag)
377 375 tag.threads.add(self)
378 376
379 377 def get_opening_post(self):
380 378 """
381 379 Get first post of the thread
382 380 """
383 381
384 382 return self.get_replies()[0]
385 383
386 384 def __unicode__(self):
387 return str(self.get_replies()[0].id)
385 return str(self.id)
388 386
389 387 def get_pub_time(self):
390 388 """
391 389 Thread does not have its own pub time, so we need to get it from
392 390 the opening post
393 391 """
394 392
395 393 return self.get_opening_post().pub_time
@@ -1,96 +1,96 b''
1 1 {% load i18n %}
2 2 {% load board %}
3 3 {% load cache %}
4 4
5 5 {% get_current_language as LANGUAGE_CODE %}
6 6
7 7 {% cache 300 post post.id post.thread_new.last_edit_time truncated moderator LANGUAGE_CODE need_open_link %}
8 8 {% spaceless %}
9 9 {% with thread=post.thread_new %}
10 10 {% if thread.archived %}
11 11 <div class="post archive_post" id="{{ post.id }}">
12 12 {% elif thread.can_bump %}
13 13 <div class="post" id="{{ post.id }}">
14 14 {% else %}
15 15 <div class="post dead_post" id="{{ post.id }}">
16 16 {% endif %}
17 17
18 18 {% if post.image %}
19 19 <div class="image">
20 20 <a
21 21 class="thumb"
22 22 href="{{ post.image.url }}"><img
23 23 src="{{ post.image.url_200x150 }}"
24 24 alt="{{ post.id }}"
25 25 width="{{ post.image_pre_width }}"
26 26 height="{{ post.image_pre_height }}"
27 27 data-width="{{ post.image_width }}"
28 28 data-height="{{ post.image_height }}"/>
29 29 </a>
30 30 </div>
31 31 {% endif %}
32 32 <div class="message">
33 33 <div class="post-info">
34 34 <span class="title">{{ post.title }}</span>
35 35 <a class="post_id" href="{% post_url post.id %}">
36 36 ({{ post.id }}) </a>
37 37 [<span class="pub_time">{{ post.pub_time }}</span>]
38 38 {% if thread.archived %}
39 39 β€” [{{ thread.bump_time }}]
40 40 {% endif %}
41 {% if not truncated %}
41 {% if not truncated and not thread.archived%}
42 42 [<a href="#" onclick="javascript:addQuickReply('{{ post.id }}')
43 43 ; return false;">&gt;&gt;</a>]
44 44 {% endif %}
45 45 {% if post.is_opening and need_open_link %}
46 46 {% if post.thread_new.archived %}
47 47 [<a class="link" href="{% url 'thread' post.id %}">{% trans "Open" %}</a>]
48 48 {% else %}
49 49 [<a class="link" href="{% url 'thread' post.id %}#form">{% trans "Reply" %}</a>]
50 50 {% endif %}
51 51 {% endif %}
52 52
53 53 {% if moderator %}
54 54 <span class="moderator_info">
55 55 [<a href="{% url 'delete' post_id=post.id %}"
56 56 >{% trans 'Delete' %}</a>]
57 57 ({{ post.poster_ip }})
58 58 [<a href="{% url 'ban' post_id=post.id %}?next={{ request.path }}"
59 59 >{% trans 'Ban IP' %}</a>]
60 60 </span>
61 61 {% endif %}
62 62 </div>
63 63 {% autoescape off %}
64 64 {% if truncated %}
65 65 {{ post.text.rendered|truncatewords_html:50 }}
66 66 {% else %}
67 67 {{ post.text.rendered }}
68 68 {% endif %}
69 69 {% endautoescape %}
70 70 {% if post.is_referenced %}
71 71 <div class="refmap">
72 72 {% trans "Replies" %}:
73 73 {% for ref_post in post.get_sorted_referenced_posts %}
74 74 <a href="{% post_url ref_post.id %}">&gt;&gt;{{ ref_post.id }}</a
75 75 >{% if not forloop.last %},{% endif %}
76 76 {% endfor %}
77 77 </div>
78 78 {% endif %}
79 79 </div>
80 80 {% if post.is_opening and thread.tags.exists %}
81 81 <div class="metadata">
82 82 {% if post.is_opening and need_open_link %}
83 83 {{ thread.get_images_count }} {% trans 'images' %}.
84 84 {% endif %}
85 85 <span class="tags">
86 86 {% for tag in thread.get_tags %}
87 87 <a class="tag" href="{% url 'tag' tag.name %}">
88 88 #{{ tag.name }}</a>{% if not forloop.last %},{% endif %}
89 89 {% endfor %}
90 90 </span>
91 91 </div>
92 92 {% endif %}
93 93 </div>
94 94 {% endwith %}
95 95 {% endspaceless %}
96 96 {% endcache %}
@@ -1,79 +1,81 b''
1 1 from django.conf.urls import patterns, url, include
2 2 from boards import views
3 3 from boards.rss import AllThreadsFeed, TagThreadsFeed, ThreadPostsFeed
4 4 from boards.views import api, tag_threads, all_threads, archived_threads, \
5 5 login, settings, all_tags
6 6 from boards.views.authors import AuthorsView
7 from boards.views.delete_post import DeletePostView
7 8
8 9 js_info_dict = {
9 10 'packages': ('boards',),
10 11 }
11 12
12 13 urlpatterns = patterns('',
13 14
14 15 # /boards/
15 16 url(r'^$', all_threads.AllThreadsView.as_view(), name='index'),
16 17 # /boards/page/
17 18 url(r'^page/(?P<page>\w+)/$', all_threads.AllThreadsView.as_view(),
18 19 name='index'),
19 20
20 21 url(r'^archive/$', archived_threads.ArchiveView.as_view(), name='archive'),
21 22 url(r'^archive/page/(?P<page>\w+)/$',
22 23 archived_threads.ArchiveView.as_view(), name='archive'),
23 24
24 25 # login page
25 26 url(r'^login/$', login.LoginView.as_view(), name='login'),
26 27
27 28 # /boards/tag/tag_name/
28 29 url(r'^tag/(?P<tag_name>\w+)/$', tag_threads.TagView.as_view(),
29 30 name='tag'),
30 31 # /boards/tag/tag_id/page/
31 32 url(r'^tag/(?P<tag_name>\w+)/page/(?P<page>\w+)/$',
32 33 tag_threads.TagView.as_view(), name='tag'),
33 34
34 35 # /boards/tag/tag_name/unsubscribe/
35 36 url(r'^tag/(?P<tag_name>\w+)/subscribe/$', views.tag_subscribe,
36 37 name='tag_subscribe'),
37 38 # /boards/tag/tag_name/unsubscribe/
38 39 url(r'^tag/(?P<tag_name>\w+)/unsubscribe/$', views.tag_unsubscribe,
39 40 name='tag_unsubscribe'),
40 41
41 42 # /boards/thread/
42 43 url(r'^thread/(?P<post_id>\w+)/$', views.thread.ThreadView.as_view(),
43 44 name='thread'),
44 45 url(r'^thread/(?P<post_id>\w+)/(?P<mode>\w+)/$', views.thread.ThreadView
45 46 .as_view(), name='thread_mode'),
46 47
47 48 url(r'^settings/$', settings.SettingsView.as_view(), name='settings'),
48 49 url(r'^tags/$', all_tags.AllTagsView.as_view(), name='tags'),
49 50 url(r'^captcha/', include('captcha.urls')),
50 51 url(r'^jump/(?P<post_id>\w+)/$', views.jump_to_post, name='jumper'),
51 52 url(r'^authors/$', AuthorsView.as_view(), name='authors'),
52 url(r'^delete/(?P<post_id>\w+)/$', views.delete, name='delete'),
53 url(r'^delete/(?P<post_id>\w+)/$', DeletePostView.as_view(),
54 name='delete'),
53 55 url(r'^ban/(?P<post_id>\w+)/$', views.ban, name='ban'),
54 56
55 57 url(r'^banned/$', views.banned.BannedView.as_view(), name='banned'),
56 58 url(r'^staticpage/(?P<name>\w+)/$', views.static_page, name='staticpage'),
57 59
58 60 # RSS feeds
59 61 url(r'^rss/$', AllThreadsFeed()),
60 62 url(r'^page/(?P<page>\w+)/rss/$', AllThreadsFeed()),
61 63 url(r'^tag/(?P<tag_name>\w+)/rss/$', TagThreadsFeed()),
62 64 url(r'^tag/(?P<tag_name>\w+)/page/(?P<page>\w+)/rss/$', TagThreadsFeed()),
63 65 url(r'^thread/(?P<post_id>\w+)/rss/$', ThreadPostsFeed()),
64 66
65 67 # i18n
66 68 url(r'^jsi18n/$', 'boards.views.cached_js_catalog', js_info_dict, name='js_info_dict'),
67 69
68 70 # API
69 71 url(r'^api/post/(?P<post_id>\w+)/$', api.get_post, name="get_post"),
70 72 url(r'^api/diff_thread/(?P<thread_id>\w+)/(?P<last_update_time>\w+)/$',
71 73 api.api_get_threaddiff, name="get_thread_diff"),
72 74 url(r'^api/threads/(?P<count>\w+)/$', api.api_get_threads,
73 75 name='get_threads'),
74 76 url(r'^api/tags/$', api.api_get_tags, name='get_tags'),
75 77 url(r'^api/thread/(?P<opening_post_id>\w+)/$', api.api_get_thread_posts,
76 78 name='get_thread'),
77 79 url(r'^api/add_post/(?P<opening_post_id>\w+)/$', api.api_add_post, name='add_post'),
78 80
79 81 )
@@ -1,49 +1,50 b''
1 1 from django.db import transaction
2 2 from django.shortcuts import render, redirect
3 3
4 4 from boards.views.base import BaseBoardView, PARAMETER_FORM
5 5 from boards.forms import SettingsForm, ModeratorSettingsForm, PlainErrorList
6 from boards.views import SETTING_MODERATE
6 7
7 8 class SettingsView(BaseBoardView):
8 9
9 10 def get(self, request):
10 11 context = self.get_context_data(request=request)
11 12 user = context['user']
12 13 is_moderator = user.is_moderator()
13 14
14 15 selected_theme = context['theme']
15 16
16 17 if is_moderator:
17 18 form = ModeratorSettingsForm(initial={'theme': selected_theme,
18 19 'moderate': context['moderator']},
19 20 error_class=PlainErrorList)
20 21 else:
21 22 form = SettingsForm(initial={'theme': selected_theme},
22 23 error_class=PlainErrorList)
23 24
24 25 context[PARAMETER_FORM] = form
25 26
26 27 return render(request, 'boards/settings.html', context)
27 28
28 29 def post(self, request):
29 30 context = self.get_context_data(request=request)
30 31 user = context['user']
31 32 is_moderator = user.is_moderator()
32 33
33 34 with transaction.atomic():
34 35 if is_moderator:
35 36 form = ModeratorSettingsForm(request.POST,
36 37 error_class=PlainErrorList)
37 38 else:
38 39 form = SettingsForm(request.POST, error_class=PlainErrorList)
39 40
40 41 if form.is_valid():
41 42 selected_theme = form.cleaned_data['theme']
42 43
43 44 user.save_setting('theme', selected_theme)
44 45
45 46 if is_moderator:
46 47 moderate = form.cleaned_data['moderate']
47 48 user.save_setting(SETTING_MODERATE, moderate)
48 49
49 50 return redirect('settings')
General Comments 0
You need to be logged in to leave comments. Login now