bgneal@75
|
1 """
|
bgneal@75
|
2 Models for the forums application.
|
bgneal@75
|
3 """
|
bgneal@102
|
4 import datetime
|
bgneal@102
|
5
|
bgneal@75
|
6 from django.db import models
|
bgneal@100
|
7 from django.db.models import Q
|
bgneal@75
|
8 from django.contrib.auth.models import User, Group
|
bgneal@387
|
9 from django.core.cache import cache
|
bgneal@1035
|
10 from django.core.urlresolvers import reverse
|
bgneal@128
|
11
|
bgneal@128
|
12 from core.markup import site_markup
|
bgneal@285
|
13 from oembed.models import Oembed
|
bgneal@75
|
14
|
bgneal@75
|
15
|
bgneal@75
|
16 class Category(models.Model):
|
bgneal@100
|
17 """
|
bgneal@100
|
18 Forums belong to a category, whose access may be assigned to groups.
|
bgneal@100
|
19 """
|
bgneal@75
|
20 name = models.CharField(max_length=80)
|
bgneal@75
|
21 slug = models.SlugField(max_length=80)
|
bgneal@75
|
22 position = models.IntegerField(blank=True, default=0)
|
bgneal@1028
|
23 groups = models.ManyToManyField(Group, blank=True,
|
bgneal@75
|
24 help_text="If groups are assigned to this category, only members" \
|
bgneal@75
|
25 " of those groups can view this category.")
|
bgneal@75
|
26
|
bgneal@75
|
27 class Meta:
|
bgneal@75
|
28 ordering = ('position', )
|
bgneal@75
|
29 verbose_name_plural = 'Categories'
|
bgneal@75
|
30
|
bgneal@75
|
31 def __unicode__(self):
|
bgneal@75
|
32 return self.name
|
bgneal@75
|
33
|
bgneal@100
|
34
|
bgneal@100
|
35 class ForumManager(models.Manager):
|
bgneal@100
|
36 """
|
bgneal@100
|
37 The manager for the Forum model. Provides a centralized place to
|
bgneal@100
|
38 put commonly used and useful queries.
|
bgneal@100
|
39 """
|
bgneal@100
|
40
|
bgneal@100
|
41 def forums_for_user(self, user):
|
bgneal@100
|
42 """
|
bgneal@100
|
43 Returns a queryset containing the forums that the given user can
|
bgneal@100
|
44 "see" due to authenticated status, superuser status and group membership.
|
bgneal@100
|
45 """
|
bgneal@167
|
46 qs = self._for_user(user)
|
bgneal@167
|
47 return qs.select_related('category', 'last_post', 'last_post__user')
|
bgneal@167
|
48
|
bgneal@167
|
49 def forum_ids_for_user(self, user):
|
bgneal@167
|
50 """Returns a list of forum IDs that the given user can "see"."""
|
bgneal@167
|
51 qs = self._for_user(user)
|
bgneal@167
|
52 return qs.values_list('id', flat=True)
|
bgneal@167
|
53
|
bgneal@170
|
54 def public_forums(self):
|
bgneal@170
|
55 """Returns a queryset containing the public forums."""
|
bgneal@170
|
56 return self.filter(category__groups__isnull=True)
|
bgneal@170
|
57
|
bgneal@387
|
58 def public_forum_ids(self):
|
bgneal@387
|
59 """
|
bgneal@387
|
60 Returns a list of ids for the public forums; the list is cached for
|
bgneal@387
|
61 performance.
|
bgneal@387
|
62 """
|
bgneal@387
|
63 public_forums = cache.get('public_forum_ids')
|
bgneal@387
|
64 if public_forums is None:
|
bgneal@387
|
65 public_forums = list(self.filter(
|
bgneal@387
|
66 category__groups__isnull=True).values_list('id', flat=True))
|
bgneal@387
|
67 cache.set('public_forum_ids', public_forums, 3600)
|
bgneal@387
|
68 return public_forums
|
bgneal@387
|
69
|
bgneal@167
|
70 def _for_user(self, user):
|
bgneal@167
|
71 """Common code for the xxx_for_user() methods."""
|
bgneal@100
|
72 if user.is_superuser:
|
bgneal@100
|
73 qs = self.all()
|
bgneal@100
|
74 else:
|
bgneal@167
|
75 user_groups = user.groups.all() if user.is_authenticated() else []
|
bgneal@167
|
76 qs = self.filter(Q(category__groups__isnull=True) |
|
bgneal@100
|
77 Q(category__groups__in=user_groups))
|
bgneal@167
|
78 return qs
|
bgneal@100
|
79
|
bgneal@75
|
80
|
bgneal@75
|
81 class Forum(models.Model):
|
bgneal@100
|
82 """
|
bgneal@100
|
83 A forum is a collection of topics.
|
bgneal@100
|
84 """
|
bgneal@1206
|
85 category = models.ForeignKey(Category, related_name='forums',
|
bgneal@1206
|
86 on_delete=models.CASCADE)
|
bgneal@75
|
87 name = models.CharField(max_length=80)
|
bgneal@75
|
88 slug = models.SlugField(max_length=80)
|
bgneal@75
|
89 description = models.TextField(blank=True, default='')
|
bgneal@75
|
90 position = models.IntegerField(blank=True, default=0)
|
bgneal@1028
|
91 moderators = models.ManyToManyField(Group, blank=True)
|
bgneal@75
|
92
|
bgneal@75
|
93 # denormalized fields to reduce database hits
|
bgneal@75
|
94 topic_count = models.IntegerField(blank=True, default=0)
|
bgneal@75
|
95 post_count = models.IntegerField(blank=True, default=0)
|
bgneal@1206
|
96 last_post = models.OneToOneField(
|
bgneal@1206
|
97 'Post', blank=True, null=True, related_name='parent_forum',
|
bgneal@1206
|
98 on_delete=models.CASCADE)
|
bgneal@75
|
99
|
bgneal@100
|
100 objects = ForumManager()
|
bgneal@100
|
101
|
bgneal@75
|
102 class Meta:
|
bgneal@75
|
103 ordering = ('position', )
|
bgneal@75
|
104
|
bgneal@75
|
105 def __unicode__(self):
|
bgneal@75
|
106 return self.name
|
bgneal@75
|
107
|
bgneal@81
|
108 def get_absolute_url(self):
|
bgneal@1035
|
109 return reverse('forums-forum_index', kwargs={'slug': self.slug})
|
bgneal@81
|
110
|
bgneal@75
|
111 def topic_count_update(self):
|
bgneal@75
|
112 """Call to notify the forum that its topic count has been updated."""
|
bgneal@75
|
113 self.topic_count = Topic.objects.filter(forum=self).count()
|
bgneal@75
|
114
|
bgneal@75
|
115 def post_count_update(self):
|
bgneal@75
|
116 """Call to notify the forum that its post count has been updated."""
|
bgneal@75
|
117 my_posts = Post.objects.filter(topic__forum=self)
|
bgneal@75
|
118 self.post_count = my_posts.count()
|
bgneal@75
|
119 if self.post_count > 0:
|
bgneal@75
|
120 self.last_post = my_posts[self.post_count - 1]
|
bgneal@75
|
121 else:
|
bgneal@75
|
122 self.last_post = None
|
bgneal@75
|
123
|
bgneal@112
|
124 def sync(self):
|
bgneal@112
|
125 """
|
bgneal@112
|
126 Call to notify the forum that it needs to recompute its
|
bgneal@112
|
127 denormalized fields.
|
bgneal@112
|
128 """
|
bgneal@112
|
129 self.topic_count_update()
|
bgneal@112
|
130 self.post_count_update()
|
bgneal@112
|
131
|
bgneal@293
|
132 def last_post_pre_delete(self, deleting_topic=False):
|
bgneal@107
|
133 """
|
bgneal@107
|
134 Call this function prior to deleting the last post in the forum.
|
bgneal@107
|
135 A new last post will be found, if one exists.
|
bgneal@107
|
136 This is to avoid the Django cascading delete issue.
|
bgneal@293
|
137 If deleting_topic is True, then the whole topic the last post is
|
bgneal@293
|
138 part of is being deleted, so we can't pick a new last post from that
|
bgneal@293
|
139 topic.
|
bgneal@107
|
140 """
|
bgneal@107
|
141 try:
|
bgneal@293
|
142 qs = Post.objects.filter(topic__forum=self)
|
bgneal@293
|
143 if deleting_topic:
|
bgneal@293
|
144 qs = qs.exclude(topic=self.last_post.topic)
|
bgneal@293
|
145 else:
|
bgneal@293
|
146 qs = qs.exclude(pk=self.last_post.pk)
|
bgneal@293
|
147
|
bgneal@293
|
148 self.last_post = qs.latest()
|
bgneal@293
|
149
|
bgneal@107
|
150 except Post.DoesNotExist:
|
bgneal@107
|
151 self.last_post = None
|
bgneal@107
|
152
|
bgneal@113
|
153 def catchup(self, user, flv=None):
|
bgneal@113
|
154 """
|
bgneal@113
|
155 Call to mark this forum all caught up for the given user (i.e. mark all topics
|
bgneal@113
|
156 read for this user).
|
bgneal@113
|
157 """
|
bgneal@113
|
158 TopicLastVisit.objects.filter(user=user, topic__forum=self).delete()
|
bgneal@113
|
159 if flv is None:
|
bgneal@113
|
160 try:
|
bgneal@113
|
161 flv = ForumLastVisit.objects.get(user=user, forum=self)
|
bgneal@113
|
162 except ForumLastVisit.DoesNotExist:
|
bgneal@113
|
163 flv = ForumLastVisit(user=user, forum=self)
|
bgneal@113
|
164
|
bgneal@113
|
165 now = datetime.datetime.now()
|
bgneal@113
|
166 flv.begin_date = now
|
bgneal@113
|
167 flv.end_date = now
|
bgneal@113
|
168 flv.save()
|
bgneal@113
|
169
|
bgneal@75
|
170
|
bgneal@75
|
171 class Topic(models.Model):
|
bgneal@100
|
172 """
|
bgneal@100
|
173 A topic is a thread of discussion, consisting of a series of posts.
|
bgneal@100
|
174 """
|
bgneal@1206
|
175 forum = models.ForeignKey(Forum, related_name='topics',
|
bgneal@1206
|
176 on_delete=models.CASCADE)
|
bgneal@75
|
177 name = models.CharField(max_length=255)
|
bgneal@407
|
178 creation_date = models.DateTimeField(db_index=True)
|
bgneal@1206
|
179 user = models.ForeignKey(User, on_delete=models.CASCADE)
|
bgneal@75
|
180 view_count = models.IntegerField(blank=True, default=0)
|
bgneal@75
|
181 sticky = models.BooleanField(blank=True, default=False)
|
bgneal@75
|
182 locked = models.BooleanField(blank=True, default=False)
|
bgneal@181
|
183 subscribers = models.ManyToManyField(User, related_name='subscriptions',
|
bgneal@386
|
184 verbose_name='subscribers', blank=True)
|
bgneal@232
|
185 bookmarkers = models.ManyToManyField(User, related_name='favorite_topics',
|
bgneal@232
|
186 verbose_name='bookmarkers', blank=True)
|
bgneal@75
|
187
|
bgneal@75
|
188 # denormalized fields to reduce database hits
|
bgneal@75
|
189 post_count = models.IntegerField(blank=True, default=0)
|
bgneal@393
|
190 update_date = models.DateTimeField(db_index=True)
|
bgneal@1206
|
191 last_post = models.OneToOneField(
|
bgneal@1206
|
192 'Post', blank=True, null=True, related_name='parent_topic',
|
bgneal@1206
|
193 on_delete=models.CASCADE)
|
bgneal@75
|
194
|
bgneal@75
|
195 class Meta:
|
bgneal@75
|
196 ordering = ('-sticky', '-update_date', )
|
bgneal@75
|
197
|
bgneal@75
|
198 def __unicode__(self):
|
bgneal@75
|
199 return self.name
|
bgneal@75
|
200
|
bgneal@82
|
201 def get_absolute_url(self):
|
bgneal@1035
|
202 return reverse('forums-topic_index', kwargs={'id': str(self.pk)})
|
bgneal@82
|
203
|
bgneal@529
|
204 def get_latest_post_url(self):
|
bgneal@1035
|
205 return reverse('forums-topic_latest', kwargs={'id': str(self.pk)})
|
bgneal@529
|
206
|
bgneal@75
|
207 def post_count_update(self):
|
bgneal@75
|
208 """
|
bgneal@75
|
209 Call this function to notify the topic instance that its post count
|
bgneal@75
|
210 has changed.
|
bgneal@75
|
211 """
|
bgneal@75
|
212 my_posts = Post.objects.filter(topic=self)
|
bgneal@75
|
213 self.post_count = my_posts.count()
|
bgneal@75
|
214 if self.post_count > 0:
|
bgneal@75
|
215 self.last_post = my_posts[self.post_count - 1]
|
bgneal@75
|
216 self.update_date = self.last_post.creation_date
|
bgneal@75
|
217 else:
|
bgneal@75
|
218 self.last_post = None
|
bgneal@75
|
219 self.update_date = self.creation_date
|
bgneal@75
|
220
|
bgneal@83
|
221 def reply_count(self):
|
bgneal@83
|
222 """
|
bgneal@83
|
223 Returns the number of replies to a topic. The first post
|
bgneal@83
|
224 doesn't count as a reply.
|
bgneal@83
|
225 """
|
bgneal@83
|
226 if self.post_count > 1:
|
bgneal@83
|
227 return self.post_count - 1
|
bgneal@83
|
228 return 0
|
bgneal@83
|
229
|
bgneal@102
|
230 def save(self, *args, **kwargs):
|
bgneal@102
|
231 if not self.id:
|
bgneal@102
|
232 now = datetime.datetime.now()
|
bgneal@102
|
233 self.creation_date = now
|
bgneal@102
|
234 self.update_date = now
|
bgneal@102
|
235
|
bgneal@102
|
236 super(Topic, self).save(*args, **kwargs)
|
bgneal@102
|
237
|
bgneal@107
|
238 def last_post_pre_delete(self):
|
bgneal@107
|
239 """
|
bgneal@107
|
240 Call this function prior to deleting the last post in the topic.
|
bgneal@107
|
241 A new last post will be found, if one exists.
|
bgneal@107
|
242 This is to avoid the Django cascading delete issue.
|
bgneal@107
|
243 """
|
bgneal@107
|
244 try:
|
bgneal@107
|
245 self.last_post = \
|
bgneal@107
|
246 Post.objects.filter(topic=self).exclude(pk=self.last_post.pk).latest()
|
bgneal@107
|
247 except Post.DoesNotExist:
|
bgneal@107
|
248 self.last_post = None
|
bgneal@107
|
249
|
bgneal@414
|
250 def search_title(self):
|
bgneal@426
|
251 if self.post_count == 1:
|
bgneal@598
|
252 post_text = "1 post"
|
bgneal@426
|
253 else:
|
bgneal@598
|
254 post_text = "%d posts" % self.post_count
|
bgneal@426
|
255
|
bgneal@598
|
256 return u"%s by %s; %s (%s)" % (self.name, self.user.username, post_text,
|
bgneal@598
|
257 self.creation_date.strftime('%d-%b-%Y'))
|
bgneal@414
|
258
|
bgneal@414
|
259 def search_summary(self):
|
bgneal@414
|
260 return u''
|
bgneal@414
|
261
|
bgneal@556
|
262 def ogp_tags(self):
|
bgneal@556
|
263 """
|
bgneal@556
|
264 Returns a dict of Open Graph Protocol meta tags.
|
bgneal@556
|
265
|
bgneal@556
|
266 """
|
bgneal@556
|
267 desc = 'Forum topic created by %s on %s.' % (
|
bgneal@556
|
268 self.user.username,
|
bgneal@556
|
269 self.creation_date.strftime('%B %d, %Y'))
|
bgneal@556
|
270
|
bgneal@556
|
271 return {
|
bgneal@556
|
272 'og:title': self.name,
|
bgneal@556
|
273 'og:type': 'article',
|
bgneal@556
|
274 'og:url': self.get_absolute_url(),
|
bgneal@556
|
275 'og:description': desc,
|
bgneal@556
|
276 }
|
bgneal@556
|
277
|
bgneal@75
|
278
|
bgneal@75
|
279 class Post(models.Model):
|
bgneal@100
|
280 """
|
bgneal@100
|
281 A post is an instance of a user's single contribution to a topic.
|
bgneal@100
|
282 """
|
bgneal@1206
|
283 topic = models.ForeignKey(Topic, related_name='posts',
|
bgneal@1206
|
284 on_delete=models.CASCADE)
|
bgneal@1206
|
285 user = models.ForeignKey(User, related_name='posts',
|
bgneal@1206
|
286 on_delete=models.CASCADE)
|
bgneal@277
|
287 creation_date = models.DateTimeField(db_index=True)
|
bgneal@277
|
288 update_date = models.DateTimeField(db_index=True)
|
bgneal@75
|
289 body = models.TextField()
|
bgneal@75
|
290 html = models.TextField()
|
bgneal@1028
|
291 user_ip = models.GenericIPAddressField(blank=True, default='', null=True)
|
bgneal@285
|
292 attachments = models.ManyToManyField(Oembed, through='Attachment')
|
bgneal@75
|
293
|
bgneal@75
|
294 class Meta:
|
bgneal@97
|
295 ordering = ('creation_date', )
|
bgneal@107
|
296 get_latest_by = 'creation_date'
|
bgneal@226
|
297 verbose_name = 'forum post'
|
bgneal@226
|
298 verbose_name_plural = 'forum posts'
|
bgneal@75
|
299
|
bgneal@91
|
300 def get_absolute_url(self):
|
bgneal@1035
|
301 return reverse('forums-goto_post', args=[str(self.pk)])
|
bgneal@91
|
302
|
bgneal@75
|
303 def summary(self):
|
bgneal@552
|
304 limit = 65
|
bgneal@552
|
305 if len(self.body) < limit:
|
bgneal@75
|
306 return self.body
|
bgneal@552
|
307 return self.body[:limit] + '...'
|
bgneal@75
|
308
|
bgneal@75
|
309 def __unicode__(self):
|
bgneal@75
|
310 return self.summary()
|
bgneal@75
|
311
|
bgneal@75
|
312 def save(self, *args, **kwargs):
|
bgneal@277
|
313 if not self.id:
|
bgneal@277
|
314 self.creation_date = datetime.datetime.now()
|
bgneal@277
|
315 self.update_date = self.creation_date
|
bgneal@277
|
316
|
bgneal@963
|
317 self.html = kwargs.pop('html', None)
|
bgneal@963
|
318 if self.html is None:
|
bgneal@963
|
319 self.html = site_markup(self.body)
|
bgneal@75
|
320 super(Post, self).save(*args, **kwargs)
|
bgneal@75
|
321
|
bgneal@75
|
322 def delete(self, *args, **kwargs):
|
bgneal@75
|
323 first_post_id = self.topic.posts.all()[0].id
|
bgneal@75
|
324 super(Post, self).delete(*args, **kwargs)
|
bgneal@75
|
325 if self.id == first_post_id:
|
bgneal@75
|
326 self.topic.delete()
|
bgneal@75
|
327
|
bgneal@113
|
328 def has_been_edited(self):
|
bgneal@277
|
329 return self.update_date > self.creation_date
|
bgneal@115
|
330
|
bgneal@115
|
331 def touch(self):
|
bgneal@115
|
332 """Call this function to indicate the post has been edited."""
|
bgneal@115
|
333 self.update_date = datetime.datetime.now()
|
bgneal@113
|
334
|
bgneal@222
|
335 def search_title(self):
|
bgneal@597
|
336 return u"%s by %s (%s)" % (self.topic.name, self.user.username,
|
bgneal@597
|
337 self.creation_date.strftime('%d-%b-%Y'))
|
bgneal@222
|
338
|
bgneal@222
|
339 def search_summary(self):
|
bgneal@222
|
340 return self.body
|
bgneal@222
|
341
|
bgneal@98
|
342
|
bgneal@98
|
343 class FlaggedPost(models.Model):
|
bgneal@98
|
344 """This model represents a user flagging a post as inappropriate."""
|
bgneal@1206
|
345 user = models.ForeignKey(User, on_delete=models.CASCADE)
|
bgneal@1206
|
346 post = models.ForeignKey(Post, on_delete=models.CASCADE)
|
bgneal@98
|
347 flag_date = models.DateTimeField(auto_now_add=True)
|
bgneal@98
|
348
|
bgneal@98
|
349 def __unicode__(self):
|
bgneal@98
|
350 return u'Post ID %s flagged by %s' % (self.post.id, self.user.username)
|
bgneal@98
|
351
|
bgneal@98
|
352 class Meta:
|
bgneal@98
|
353 ordering = ('flag_date', )
|
bgneal@98
|
354
|
bgneal@98
|
355 def get_post_url(self):
|
bgneal@98
|
356 return '<a href="%s">Post</a>' % self.post.get_absolute_url()
|
bgneal@98
|
357 get_post_url.allow_tags = True
|
bgneal@98
|
358
|
bgneal@113
|
359
|
bgneal@113
|
360 class ForumLastVisit(models.Model):
|
bgneal@113
|
361 """
|
bgneal@113
|
362 This model records the last time a user visited a forum.
|
bgneal@113
|
363 It is used to compute if a user has unread topics in a forum.
|
bgneal@113
|
364 We keep track of a window of time, delimited by begin_date and end_date.
|
bgneal@113
|
365 Topics updated within this window are tracked, and may have TopicLastVisit
|
bgneal@113
|
366 objects.
|
bgneal@113
|
367 Marking a forum as all read sets the begin_date equal to the end_date.
|
bgneal@113
|
368 """
|
bgneal@1206
|
369 user = models.ForeignKey(User, on_delete=models.CASCADE)
|
bgneal@1206
|
370 forum = models.ForeignKey(Forum, on_delete=models.CASCADE)
|
bgneal@113
|
371 begin_date = models.DateTimeField()
|
bgneal@113
|
372 end_date = models.DateTimeField()
|
bgneal@113
|
373
|
bgneal@113
|
374 class Meta:
|
bgneal@113
|
375 unique_together = ('user', 'forum')
|
bgneal@113
|
376 ordering = ('-end_date', )
|
bgneal@113
|
377
|
bgneal@113
|
378 def __unicode__(self):
|
bgneal@113
|
379 return u'Forum: %d User: %d Date: %s' % (self.forum.id, self.user.id,
|
bgneal@113
|
380 self.end_date.strftime('%Y-%m-%d %H:%M:%S'))
|
bgneal@113
|
381
|
bgneal@113
|
382 def is_caught_up(self):
|
bgneal@113
|
383 return self.begin_date == self.end_date
|
bgneal@113
|
384
|
bgneal@113
|
385
|
bgneal@113
|
386 class TopicLastVisit(models.Model):
|
bgneal@113
|
387 """
|
bgneal@113
|
388 This model records the last time a user read a topic.
|
bgneal@113
|
389 Objects of this class exist for the window specified in the
|
bgneal@113
|
390 corresponding ForumLastVisit object.
|
bgneal@113
|
391 """
|
bgneal@1206
|
392 user = models.ForeignKey(User, on_delete=models.CASCADE)
|
bgneal@1206
|
393 topic = models.ForeignKey(Topic, on_delete=models.CASCADE)
|
bgneal@407
|
394 last_visit = models.DateTimeField(db_index=True)
|
bgneal@113
|
395
|
bgneal@113
|
396 class Meta:
|
bgneal@113
|
397 unique_together = ('user', 'topic')
|
bgneal@113
|
398 ordering = ('-last_visit', )
|
bgneal@113
|
399
|
bgneal@113
|
400 def __unicode__(self):
|
bgneal@113
|
401 return u'Topic: %d User: %d Date: %s' % (self.topic.id, self.user.id,
|
bgneal@113
|
402 self.last_visit.strftime('%Y-%m-%d %H:%M:%S'))
|
bgneal@113
|
403
|
bgneal@113
|
404 def save(self, *args, **kwargs):
|
bgneal@445
|
405 if self.last_visit is None:
|
bgneal@113
|
406 self.touch()
|
bgneal@113
|
407 super(TopicLastVisit, self).save(*args, **kwargs)
|
bgneal@293
|
408
|
bgneal@113
|
409 def touch(self):
|
bgneal@113
|
410 self.last_visit = datetime.datetime.now()
|
bgneal@164
|
411
|
bgneal@285
|
412
|
bgneal@285
|
413 class Attachment(models.Model):
|
bgneal@285
|
414 """
|
bgneal@285
|
415 This model is a "through" table for the M2M relationship between forum
|
bgneal@285
|
416 posts and Oembed objects.
|
bgneal@285
|
417 """
|
bgneal@1206
|
418 post = models.ForeignKey(Post, on_delete=models.CASCADE)
|
bgneal@1206
|
419 embed = models.ForeignKey(Oembed, on_delete=models.CASCADE)
|
bgneal@285
|
420 order = models.IntegerField()
|
bgneal@285
|
421
|
bgneal@285
|
422 class Meta:
|
bgneal@285
|
423 ordering = ('order', )
|
bgneal@285
|
424
|
bgneal@285
|
425 def __unicode__(self):
|
bgneal@285
|
426 return u'Post %d, %s' % (self.post.pk, self.embed.title)
|
bgneal@301
|
427
|