models.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859
  1. import os
  2. from datetime import datetime, timedelta
  3. from django.conf import settings
  4. from django.contrib.auth.models import Group, User
  5. from django.contrib.contenttypes import generic
  6. from django.contrib.contenttypes.models import ContentType
  7. from django.contrib.sites.managers import CurrentSiteManager
  8. from django.contrib.sites.models import Site
  9. from django.db import models
  10. from django.utils.safestring import mark_safe
  11. from django.utils.translation import ugettext_lazy as _
  12. from django_extensions.db.fields import AutoSlugField
  13. from django_extensions.db.models import TimeStampedModel
  14. from django_inlines import inlines
  15. from taggit.managers import TaggableManager
  16. from taggit.models import Tag
  17. from darkroom.inlines import GraphicInline, PhotoInline
  18. from darkroom.models import Gallery, Graphic, Movie, Photo, Slideshow
  19. from directory.models import Person, Place, Town
  20. from documents.models import Document, PDFDocument
  21. from newsroom.managers import (
  22. AdvantagesEditionManager,
  23. CompassEditionManager,
  24. EditionManager,
  25. EditorialManager,
  26. PacketEditionManager,
  27. PatriotEditionManager,
  28. StoryManager,
  29. )
  30. from remember.models import Announcement, InMemoriam, Remembrance, Service
  31. inlines.registry.register('photo', PhotoInline)
  32. inlines.registry.register('graphic', GraphicInline)
  33. # Get relative media path
  34. NEWSROOM_DIR = getattr(settings, 'NEWSROOM_DIR', 'newsroom/')
  35. MARKUP_HTML = 'h'
  36. MARKUP_MARKDOWN = 'm'
  37. MARKUP_REST = 'r'
  38. MARKUP_TEXTILE = 't'
  39. MARKUP_OPTIONS = getattr(
  40. settings,
  41. 'ARTICLE_MARKUP_OPTIONS',
  42. (
  43. (MARKUP_MARKDOWN, _('Markdown')),
  44. (MARKUP_HTML, _('HTML/Plain Text')),
  45. (MARKUP_REST, _('ReStructured Text')),
  46. (MARKUP_TEXTILE, _('Textile'))
  47. )
  48. )
  49. MARKUP_DEFAULT = getattr(settings, 'COPY_MARKUP_DEFAULT', MARKUP_MARKDOWN)
  50. AUTO_TAG = getattr(settings, 'NEWSROOM_AUTO_TAG', True)
  51. class Paper(TimeStampedModel):
  52. name = models.CharField(_('name'), max_length=100)
  53. towns = models.ManyToManyField(Town)
  54. slug = AutoSlugField(_('slug'), populate_from='name', editable=True)
  55. description = models.TextField(_('description'), blank=True, null=True)
  56. class Meta:
  57. verbose_name = _('paper')
  58. verbose_name_plural = _('papers')
  59. ordering = ('name',)
  60. def __unicode__(self):
  61. return self.name
  62. class Link(TimeStampedModel):
  63. url = models.URLField('URL')
  64. title = models.CharField(_('title'), max_length=255)
  65. description = models.CharField(_('description'), max_length=200, blank=True, null=True)
  66. published = models.BooleanField(_('published'), default=False)
  67. published_on = models.DateTimeField(_('published on'))
  68. class Meta:
  69. verbose_name = _('link')
  70. verbose_name_plural = _('links')
  71. ordering = ('created',)
  72. get_latest_by = 'published_on'
  73. def __unicode__(self):
  74. return self.title
  75. @models.permalink
  76. def get_absolute_url(self):
  77. return ('link_detail', (), {'slug': self.slug})
  78. class AuthorType(models.Model):
  79. title = models.CharField(_('name'), max_length=100)
  80. slug = AutoSlugField(_('slug'), populate_from='title', editable=True)
  81. description = models.TextField(_('description'), blank=True, null=True)
  82. class Meta:
  83. ordering = ('title',)
  84. def __unicode__(self):
  85. return self.title
  86. @models.permalink
  87. def get_absolute_url(self):
  88. return ('author_type_detail', (), {'slug': self.slug})
  89. class Author(TimeStampedModel):
  90. name = models.CharField(_('name'), max_length=100)
  91. slug = AutoSlugField(_('slug'), populate_from='name', editable=True)
  92. fixed_initials = models.CharField(_('initials'), blank=True, null=True, max_length=5)
  93. type = models.ForeignKey(AuthorType)
  94. title = models.CharField(_('Title'), blank=True, null=True, max_length=100)
  95. organization = models.ForeignKey(Place, blank=True, null=True)
  96. about = models.TextField(_('about'), blank=True, null=True)
  97. user = models.ForeignKey(User, blank=True, null=True)
  98. def initials(self):
  99. if self.fixed_initials:
  100. return self.fixed_initials
  101. else:
  102. initials = ''
  103. for i in self.name.split(" "):
  104. initials += i[0]
  105. return initials
  106. def __unicode__(self):
  107. return self.name
  108. @models.permalink
  109. def get_absolute_url(self):
  110. return ('nr-author-detail', (), {'type_slug': self.type.slug, 'slug': self.slug})
  111. class Meta:
  112. verbose_name = _('author')
  113. verbose_name_plural = _('authors')
  114. ordering = ('name',)
  115. get_latest_by = 'created'
  116. class AttachedLink(models.Model):
  117. content_type = models.ForeignKey(ContentType)
  118. object_id = models.IntegerField(db_index=True)
  119. content_object = generic.GenericForeignKey()
  120. link = models.ForeignKey(Link)
  121. def __unicode__(self):
  122. return self.link.title
  123. class AttachedGraphic(models.Model):
  124. content_type = models.ForeignKey(ContentType)
  125. object_id = models.IntegerField(db_index=True)
  126. content_object = generic.GenericForeignKey()
  127. graphic = models.ForeignKey(Graphic)
  128. def __unicode__(self):
  129. return self.graphic.title
  130. class AttachedMovie(models.Model):
  131. content_type = models.ForeignKey(ContentType)
  132. object_id = models.IntegerField(db_index=True)
  133. content_object = generic.GenericForeignKey()
  134. movie = models.ForeignKey(Movie)
  135. def __unicode__(self):
  136. return self.movie.title
  137. class AttachedSlideshow(models.Model):
  138. content_type = models.ForeignKey(ContentType)
  139. object_id = models.IntegerField(db_index=True)
  140. content_object = generic.GenericForeignKey()
  141. slideshow = models.ForeignKey(Slideshow)
  142. def __unicode__(self):
  143. return self.slideshow.title
  144. class AttachedDocument(TimeStampedModel):
  145. content_type = models.ForeignKey(ContentType)
  146. object_id = models.IntegerField(db_index=True)
  147. content_object = generic.GenericForeignKey()
  148. document = models.ForeignKey(Document)
  149. class Meta:
  150. ordering = ['-created']
  151. get_latest_by = 'created'
  152. def __unicode__(self):
  153. return self.document.title
  154. def get_absolute_url(self):
  155. return self.document.get_absolute_url()
  156. def doc_dir(self):
  157. return os.path.dirname(self.get_file_filename())
  158. def remove_dirs(self):
  159. if os.path.isdir(self.doc_dir()):
  160. if os.listdir(self.doc_dir()) == []:
  161. os.removedirs(self.doc_dir())
  162. def delete(self):
  163. super(AttachedDocument, self).delete()
  164. self.remove_dirs()
  165. class AttachedGallery(models.Model):
  166. content_type = models.ForeignKey(ContentType)
  167. object_id = models.IntegerField(db_index=True)
  168. content_object = generic.GenericForeignKey()
  169. gallery = models.ForeignKey(Gallery)
  170. def __unicode__(self):
  171. return self.gallery.title
  172. class Dateline(TimeStampedModel):
  173. """A dateline specifies where a story's action takes place.
  174. The reason for the separate model is so that the string displayed
  175. can be custom, while the towns the dateline refers to are predetermined
  176. in the database. Thus a date line of: One Town and Anotehr Town or
  177. Our Community, can have specific locations related to them, while
  178. being able to be displayed per editorial spec."""
  179. title = models.CharField(_('title'), max_length=100)
  180. slug = AutoSlugField(_('slug'), populate_from='title', editable=True)
  181. towns = models.ManyToManyField(Town)
  182. class Meta:
  183. ordering = ('title',)
  184. def __unicode__(self):
  185. return self.title
  186. @models.permalink
  187. def get_absolute_url(self):
  188. return ('dateline_detail', (), {'slug': self.slug})
  189. MARKUP_HELP = _(
  190. """Select the type of markup you are using in this article.
  191. <ul>
  192. <li><a href="http://daringfireball.net/projects/markdown/basics" target="_blank">Markdown Guide</a></li>
  193. <li><a href="http://docutils.sourceforge.net/docs/user/rst/quickref.html" target="_blank">ReStructured Text Guide</a></li>
  194. <li><a href="http://thresholdstate.com/articles/4312/the-textile-reference-manual" target="_blank">Textile Guide</a></li>
  195. </ul>"""
  196. )
  197. class StandingHed(TimeStampedModel):
  198. '''Standing Hed model.
  199. In lieu of a proper column or blog application, this is a hack to get some
  200. stories to always be associated with one author or column.
  201. '''
  202. title = models.CharField(_('Title'), max_length=100)
  203. slug = AutoSlugField(_('Slug'), populate_from='title', editable=True)
  204. def __unicode__(self):
  205. return self.title
  206. class Copy(TimeStampedModel):
  207. '''Copy model.
  208. An abstract model for anything that can be copy.
  209. By default, we have two choices for heds.
  210. If a different web_hed isn't specified, we use the one from print.
  211. '''
  212. print_hed = models.CharField(_('print headline'), max_length=255)
  213. web_hed = models.CharField(_('web headline'), max_length=255)
  214. standing_hed = models.ForeignKey(StandingHed, blank=True, null=True)
  215. slug = AutoSlugField(_('slug'), populate_from='web_hed', editable=True)
  216. kicker = models.CharField(_('kicker'), max_length=255, blank=True, null=True)
  217. subhed = models.CharField(_('sub-headline'), max_length=255, blank=True, null=True)
  218. body = models.TextField(_('body'))
  219. summary = models.TextField(_('summary'), blank=True)
  220. markup = models.CharField(max_length=1, choices=MARKUP_OPTIONS, default=MARKUP_DEFAULT, help_text=MARKUP_HELP)
  221. published = models.BooleanField(_('publish'), default=False)
  222. published_on = models.DateTimeField(_('publish date'), default=datetime.now())
  223. paper_editions = models.ManyToManyField('PaperEdition', blank=True, null=True)
  224. sites = models.ManyToManyField(Site)
  225. view_count = models.PositiveIntegerField(default=0, editable=False)
  226. created_by = models.ForeignKey(User, editable=False)
  227. auto_tag = models.BooleanField(_('auto-tag'), default=True)
  228. tags = TaggableManager(blank=True)
  229. class Meta:
  230. abstract = True
  231. ordering = ('-published_on', 'print_hed')
  232. get_latest_by = 'published_on'
  233. @property
  234. def hed(self):
  235. if self.web_hed:
  236. return self.web_hed
  237. else:
  238. return self.print_hed
  239. @property
  240. def paragraphs(self):
  241. """
  242. Return the paragraphs as a list
  243. """
  244. import re
  245. if self.markup == 'm':
  246. import markdown
  247. html=markdown.markdown(self.body)
  248. elif self.markup == 't':
  249. import textile
  250. html=textile.textile(self.body)
  251. elif self.markup == 'r':
  252. html = 'Cant figure out how to implement RST yet...'
  253. else:
  254. html = self.body
  255. return re.findall("(<p.+?>.+?</p>)", html, re.I | re.S)
  256. def do_auto_tag(self):
  257. """
  258. Performs the auto-tagging work if necessary.
  259. Returns True if an additional save is required, False otherwise.
  260. """
  261. found = False
  262. if self.auto_tag:
  263. import re
  264. # don't clobber any existing tags!
  265. existing_ids = [t.id for t in self.tags.all()]
  266. unused = Tag.objects.all()
  267. unused = unused.exclude(id__in=existing_ids)
  268. for tag in unused:
  269. regex = re.compile(r'\b%s\b' % tag.name, re.I)
  270. regex_found = (
  271. regex.search(self.body)
  272. or regex.search(self.web_hed)
  273. or regex.search(self.print_hed)
  274. or regex.search(self.subhed)
  275. or regex.search(self.kicker)
  276. )
  277. if regex_found:
  278. self.tags.add(tag)
  279. found = True
  280. return found
  281. def do_scrub_rich_text(self):
  282. scrubbed = False
  283. if self.markup == 'h':
  284. import re
  285. from django.template.defaultfilters import removetags
  286. self.body = re.sub('\n', ' ', re.sub('style=".*?"', '', re.sub('align=".*?"', '', removetags(
  287. self.body, "font br"
  288. ))))
  289. scrubbed = True
  290. return scrubbed
  291. def do_set_summary(self):
  292. summarized = False
  293. if (len(self.paragraphs[0].split(' ')) > 100) or (len(self.paragraphs) == 1):
  294. self.summary = self.paragraphs[0]
  295. summarized=True
  296. elif len(self.paragraphs) == 0:
  297. summarized = False
  298. else:
  299. self.summary = self.paragraphs[0] + self.paragraphs[1]
  300. summarized = True
  301. return summarized
  302. def save(self, *args, **kwargs):
  303. self.do_set_summary()
  304. super(Copy, self).save(*args, **kwargs)
  305. self.do_auto_tag()
  306. self.do_scrub_rich_text()
  307. super(Copy, self).save(*args, **kwargs)
  308. class Story(Copy):
  309. """A story is one of our primary types of content"""
  310. STATUS_CHOICES = ((0, 'None'), (1, 'Primary'), (2, 'Secondary'), )
  311. dateline = models.ForeignKey(Dateline)
  312. authors = models.ManyToManyField(Author, blank=True, null=True)
  313. lead_photo = models.ForeignKey(Photo, blank=True, null=True)
  314. cropped_photo = models.ImageField(_('cropped photo'), blank=True, null=True,upload_to='newsroom/cropped_photos/')
  315. status = models.IntegerField(_('status'), choices=STATUS_CHOICES, default=0)
  316. towns = models.ManyToManyField(Town)
  317. weight = models.IntegerField(
  318. _('Weight'),
  319. null=True,
  320. blank=True,
  321. default=0,
  322. help_text='Stories weighted higher appear before those with lower weights.',
  323. )
  324. press_release = models.BooleanField(_('press release'), default=False)
  325. source = models.ForeignKey(Place, blank=True, null=True, help_text="Source of original copy if is a press release.")
  326. attached_documents = generic.GenericRelation(AttachedDocument)
  327. attached_gallery = generic.GenericRelation(AttachedGallery)
  328. attached_graphic = generic.GenericRelation(AttachedGraphic)
  329. attached_movie = generic.GenericRelation(AttachedMovie)
  330. attached_slideshow = generic.GenericRelation(AttachedSlideshow)
  331. # Custom story manager (see manager.py)
  332. objects = StoryManager()
  333. on_site = CurrentSiteManager('sites')
  334. class Meta:
  335. verbose_name_plural = ('stories')
  336. ordering = ('-published_on', 'web_hed', 'print_hed')
  337. get_latest_by = 'published_on'
  338. def prod_slug(self):
  339. authors=''
  340. for a in self.authors.all():
  341. authors = a.initials()
  342. if authors:
  343. return u'%s-%s' % (authors, self.slug)
  344. else:
  345. return u'PR-%s' % (self.slug)
  346. def __unicode__(self):
  347. return self.hed
  348. def editions(self):
  349. list = ""
  350. for e in self.edition_set.all():
  351. list += e.paper.name +" " + e.published_on.strftime("%m-%d-%y") + "; "
  352. return list
  353. @property
  354. def author(self):
  355. #link='<a href="%s" title="%s">%s</a>'
  356. string='%s'
  357. #authors=[link % (a.get_absolute_url(), a, a.name) for a in self.authors.all()]
  358. authors = [string % (a.name) for a in self.authors.all()]
  359. if len(authors) > 1:
  360. author_string="%s and %s" % (", ".join(authors[:-1]), authors[-1])
  361. if len(authors) == 1:
  362. author_string=authors[0]
  363. else:
  364. author_string = ''
  365. return mark_safe(author_string)
  366. @models.permalink
  367. def get_absolute_url(self, site=''):
  368. return ('nr-story-detail', (), {
  369. 'year': self.published_on.year,
  370. 'month': self.published_on.strftime('%b').lower(),
  371. 'day': self.published_on.day,
  372. 'slug': self.slug})
  373. def get_admin_url(self):
  374. return u'admin/newsroom/story/%s/' % (str(self.id))
  375. def get_relative_url(self):
  376. return u'news/%s/%s/%s/%s' % (self.published_on.year, self.published_on.strftime('%b').lower(), self.published_on.day, self.slug)
  377. class EditorialType(models.Model):
  378. '''Editorial Type model.
  379. A type of editorial...
  380. '''
  381. name= models.CharField(_('name'), max_length=100)
  382. slug = AutoSlugField(_('slug'), populate_from='name', editable=True)
  383. description = models.TextField(_('description'), blank=True, null=True)
  384. def __unicode__(self):
  385. return self.name
  386. @models.permalink
  387. def get_absolute_url(self):
  388. return ('editorial_type_detail', (), {'slug':self.slug})
  389. class Editorial(Copy):
  390. '''Editorial model.
  391. Manages editorial content: letter to the editor, editor's notes, corrections.
  392. '''
  393. dateline = models.ForeignKey(Dateline, blank=True, null=True)
  394. type = models.ForeignKey(EditorialType)
  395. author = models.ForeignKey(Author, blank=True, null=True)
  396. citizen_authors = models.ManyToManyField(Person, blank=True, null=True)
  397. towns = models.ManyToManyField(Town)
  398. # Custom story manager (see manager.py)
  399. objects = EditorialManager()
  400. on_site = CurrentSiteManager('sites')
  401. class Meta:
  402. verbose_name_plural=('editorials')
  403. ordering=('-published_on', 'web_hed', 'print_hed')
  404. get_latest_by='published_on'
  405. def __unicode__(self):
  406. return self.web_hed
  407. def editions(self):
  408. list = ""
  409. for e in self.edition_set.all():
  410. list += e.paper.name +" " + e.published_on.strftime("%m-%d-%y") + "; "
  411. return list
  412. @models.permalink
  413. def get_absolute_url(self, site=''):
  414. return ('nr-editorial-detail', (), {
  415. 'year': self.published_on.year,
  416. 'month': self.published_on.strftime('%b').lower(),
  417. 'day': self.published_on.day,
  418. 'slug': self.slug})
  419. def get_relative_url(self):
  420. return u'%s/%s/%s/%s' % (self.published_on.year, self.published_on.strftime('%b').lower(), self.published_on.day, self.slug)
  421. class PaperEdition(TimeStampedModel):
  422. paper = models.ForeignKey(Paper)
  423. published_on = models.DateField(_('publish on'))
  424. published = models.BooleanField(_('published'), default=False)
  425. site = models.ManyToManyField(Site)
  426. class Meta:
  427. verbose_name=_('Paper edition')
  428. verbose_name_plural=_('Paper editions')
  429. ordering=('-published_on', 'paper')
  430. get_latest_by='published_on'
  431. def __unicode__(self):
  432. return u'%s published %s' % (self.paper, self.published_on)
  433. class WebEdition(TimeStampedModel):
  434. paper = models.ForeignKey(Paper)
  435. published_on = models.DateField(_('publish on'))
  436. published = models.BooleanField(_('published'), default=False)
  437. site = models.ForeignKey(Site)
  438. featured_photo = models.ForeignKey(Photo, null=True, blank=True, related_name="featured_photo")
  439. featured_story = models.ForeignKey(Story, related_name="featured_story", null=True, blank=True)
  440. stories = models.ManyToManyField(
  441. Story,
  442. blank=True,
  443. null=True,
  444. help_text="Include the featured story in this list of all stories in this edition.",
  445. limit_choices_to={'published_on__gte': datetime.today()-timedelta(days=45)},
  446. )
  447. photos = models.ManyToManyField(
  448. Photo,
  449. blank=True,
  450. null=True,
  451. related_name="photos",
  452. limit_choices_to={'published_on__gte': datetime.today()-timedelta(days=45)},
  453. )
  454. galleries = models.ManyToManyField(
  455. Gallery,
  456. blank=True,
  457. null=True,
  458. limit_choices_to={'published_on__gte': datetime.today()-timedelta(days=45)},
  459. )
  460. movies = models.ManyToManyField(Movie, blank=True, null=True)
  461. slideshows = models.ManyToManyField(Slideshow, blank=True, null=True)
  462. editorials = models.ManyToManyField(Editorial, blank=True, null=True)
  463. pdfdocuments = models.ManyToManyField(PDFDocument, blank=True, null=True)
  464. remembrances = models.ManyToManyField(
  465. Remembrance,
  466. blank=True,
  467. null=True,
  468. limit_choices_to={'published_on__gte': datetime.today()-timedelta(days=45)},
  469. )
  470. death_announcements = models.ManyToManyField(
  471. Announcement,
  472. blank=True,
  473. null=True,
  474. limit_choices_to={'published_on__gte': datetime.today()-timedelta(days=45)},
  475. )
  476. memorial_services = models.ManyToManyField(
  477. Service,
  478. blank=True,
  479. null=True,
  480. limit_choices_to={'published_on__gte': datetime.today()-timedelta(days=45)},
  481. )
  482. inmemoriams = models.ManyToManyField(
  483. InMemoriam,
  484. blank=True,
  485. null=True,
  486. limit_choices_to={'published_on__gte': datetime.today()-timedelta(days=45)},
  487. )
  488. objects=EditionManager()
  489. on_site = CurrentSiteManager('site')
  490. # per-paper managers
  491. patriot = PatriotEditionManager()
  492. advantages = AdvantagesEditionManager()
  493. packet = PacketEditionManager()
  494. compass = CompassEditionManager()
  495. class Meta:
  496. verbose_name =_('Web edition')
  497. verbose_name_plural =_('Web editions')
  498. ordering = ('-published_on', 'paper')
  499. get_latest_by = 'published_on'
  500. def __unicode__(self):
  501. return u'Web edition of %s published on %s' % (self.paper, self.published_on)
  502. '''
  503. def save(self):
  504. if not self.id:
  505. super(Edition, self).save(*args, **kwargs)
  506. if self.published==True:
  507. self.stories.all().update(published=True)
  508. self.galleries.all().update(published=True)
  509. self.slideshows.all().update(published=True)
  510. self.movies.all().update(published=True)
  511. self.editorials.all().update(published=True)
  512. for p in self.photos.all():
  513. p.published=True
  514. p.save()
  515. for s in self.stories.all():
  516. if s.lead_photo:
  517. s.lead_photo.published=True
  518. s.lead_photo.save()
  519. s.photos.all().update(published=True)
  520. for g in s.galleries.all():
  521. for p in g.photos.all():
  522. p.published=True
  523. p.save()
  524. for g in self.galleries.all():
  525. for p in g.photos.all():
  526. p.published=True
  527. p.save()
  528. for a in self.death_announcements.all():
  529. a.published=True
  530. a.save()
  531. for s in self.memorial_services.all():
  532. s.published=True
  533. s.save()
  534. for i in self.inmemoriams.all():
  535. i.published=True
  536. i.save()
  537. for r in self.remembrances.all():
  538. r.published=True
  539. r.save()
  540. else:
  541. self.stories.all().update(published=False)
  542. self.galleries.all().update(published=False)
  543. self.photos.all().update(published=False)
  544. self.slideshows.all().update(published=False)
  545. self.movies.all().update(published=False)
  546. self.editorials.all().update(published=False)
  547. for s in self.stories.all():
  548. if s.lead_photo:
  549. s.lead_photo.published=False
  550. s.lead_photo.save()
  551. for g in self.galleries.all():
  552. for p in g.photos.all():
  553. p.published=False
  554. p.save()
  555. for a in self.death_announcements.all():
  556. a.published=False
  557. a.save()
  558. for s in self.memorial_services.all():
  559. s.published=False
  560. s.save()
  561. for i in self.inmemoriams.all():
  562. i.published=False
  563. i.save()
  564. for r in self.remembrances.all():
  565. r.published=False
  566. r.save()
  567. super(WebEdition, self).save(*args, **kwargs)
  568. '''
  569. class ArchiveItem(TimeStampedModel):
  570. '''A base class inherited by whatever content class we want to stuff in
  571. the archive.
  572. Items are associated with a particular paper, can expire and belong
  573. to "groups"
  574. The groups allow for very basic access control, so we could sell access to
  575. certain archive items.
  576. '''
  577. archived_on = models.DateTimeField(_('Archived on'), default=datetime.now())
  578. expires_on = models.DateTimeField(_('Expires on'), blank=True, null=True)
  579. groups = models.ManyToManyField(Group, blank=True, null=True)
  580. paper = models.ForeignKey(Paper)
  581. class ArchiveSection(ArchiveItem):
  582. '''Archive section model.
  583. This model is used to collect editioral content into a produced section
  584. The group can then be displayed on as an archive on a particular subject.
  585. It actually duplicates a lot of what's in a story because section objects
  586. don't exist in and of their own like stories or documents.
  587. '''
  588. GROUP_BY_CHOICES = (
  589. ('type', _('Type')),
  590. ('dline', _('Dateline')),
  591. ('both', _('Both')),
  592. )
  593. title = models.CharField(_('title'), max_length=200)
  594. slug = AutoSlugField(_('slug'), populate_from='title', editable=True)
  595. description = models.TextField(_('body'))
  596. published = models.BooleanField(_('published'), default=False)
  597. published_on = models.DateTimeField(_('published on'))
  598. thumbnail = models.ImageField(_('Thumbnail'), blank=True, null=True, upload_to="newsroom/archives/thumbnails/")
  599. lead_image = models.ImageField(_('Lead image'), blank=True, null=True, upload_to="newsroom/archives/lead_images/")
  600. dateline = models.ForeignKey(Dateline, blank=True, null=True)
  601. stories = models.ManyToManyField(Story, blank=True, null=True)
  602. editorials = models.ManyToManyField(Editorial, blank=True, null=True)
  603. pdf_documents = models.ManyToManyField(PDFDocument, blank=True, null=True)
  604. photos = models.ManyToManyField(Photo, blank=True, null=True)
  605. galleries = models.ManyToManyField(Gallery, blank=True, null=True)
  606. movies = models.ManyToManyField(Movie, blank=True, null=True)
  607. slideshows = models.ManyToManyField(Slideshow, blank=True, null=True)
  608. group_by = models.CharField(_('Group by'), choices=GROUP_BY_CHOICES,
  609. default='type', max_length=10)
  610. class Meta:
  611. verbose_name=_('archive section')
  612. verbose_name_plural=('archive sections')
  613. ordering=['-modified']
  614. def __unicode__(self):
  615. return u'%s' % self.title
  616. @models.permalink
  617. def get_absolute_url(self):
  618. return ('nr-archive-section-detail', (), {'year':self.published_on.year, 'slug':self.slug})
  619. def get_relative_url(self):
  620. return u'%s/%s/' % (self.published_on.year, self.slug)
  621. class ArchiveStory(ArchiveItem):
  622. """A wrapper class for putting stories into our "archive" section"""
  623. story = models.ForeignKey(Story)
  624. class Meta:
  625. verbose_name=_('Archived story')
  626. verbose_name_plural=_('Archived stories')
  627. def __unicode__(self):
  628. return u'%s' % self.story.hed()
  629. @models.permalink
  630. def get_absolute_url(self):
  631. return self.story.get_absolute_url
  632. class ArchiveMovie(ArchiveItem):
  633. """A wrapper class for putting movies into our "archive" section"""
  634. movie = models.ForeignKey(Movie)
  635. class Meta:
  636. verbose_name=_('Archived movie')
  637. verbose_name_plural=_('Archived movies')
  638. def __unicode__(self):
  639. return u'%s' % self.movie.title()
  640. @models.permalink
  641. def get_absolute_url(self):
  642. return self.movie.get_absolute_url
  643. class ArchiveSlideshow(ArchiveItem):
  644. """A wrapper class for putting slideshows into our "archive" section"""
  645. slideshow = models.ForeignKey(Slideshow)
  646. class Meta:
  647. verbose_name =_('Archived slideshow')
  648. verbose_name_plural =_('Archived slideshows')
  649. def __unicode__(self):
  650. return u'%s' % self.slideshow.title()
  651. @models.permalink
  652. def get_absolute_url(self):
  653. return self.slideshow.get_absolute_url
  654. class ArchiveDocument(ArchiveItem):
  655. """We need a way to stick documents in the archive..."""
  656. document = models.ForeignKey(Document)
  657. class Meta:
  658. verbose_name =_('Archived document')
  659. verbose_name_plural =_('Archived documents')
  660. def __unicode__(self):
  661. return u'%s' % self.document.title
  662. @models.permalink
  663. def get_absolute_url(self):
  664. return self.document.get_absolute_url
  665. '''
  666. class PageType(models.Model):
  667. name = models.CharField(_('name'), max_length=150)
  668. slug = AutoSlugField(_('slug'), populate_from='name', editable=True)
  669. description = models.TextField(_('description'), blank=True, null=True)
  670. class Meta:
  671. verbose_name=_('page type')
  672. verbose_name_plural=('page types')
  673. def __unicode__(self):
  674. return u'%s' % (self.name)
  675. class Page(models.Model):
  676. number = models.IntegerField(_('number'), max_length=3)
  677. edition = models.ForeignKey(Edition)
  678. copy_inches = models.IntegerField(_('inches of copy'), max_length=3, blank=True, null=True, help_text='If left blank, will be calcuated by included copy.' )
  679. ad_inches = models.IntegerField(_('inches of ads'), max_length=3, blank=True, null=True, help_text='If left blank, will be calcuated by included ads.' )
  680. ad_group = models.ForeignKey(AdCategory, null=True, blank=True, help_text='Primary ad group for page.')
  681. ad_notes = models.TextField(_('ad notes'), null=True, blank=True)
  682. ad_spec_pos = models.TextField(_('ad special position'), null=True, blank=True)
  683. ads = models.ManyToManyField(BannerAd, blank=True, null=True)
  684. copy = models.ManyToManyField(Story, blank=True, null=True )
  685. photos = models.ManyToManyField(Photo, blank=True, null=True )
  686. type = models.ForeignKey(PageType, blank=True, null=True )
  687. class Meta:
  688. verbose_name=_('page')
  689. verbose_name_plural=('pages')
  690. unique_together = (("number", "edition"),)
  691. def __unicode__(self):
  692. return u'Page %s of %s' % (self.number, self.edition)
  693. '''