Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- class Title(models.Model):
- # In the DB, title_slug is a VARCHAR(50)
- title_slug = models.SlugField((u'Slug'), unique=True, help_text="This is *very* important. This value, along with the Primary Author Slug, is used to define the URL for this review. It must be unique, and garbage characters in here make us (and our customers) look bad. However, if you change this, you need to also setup a Redirect to map the old URL to the new one.")
- # What issue this Title/Review was published in
- issue = models.ForeignKey('issues.Issue', blank=True, null=True, related_name='titles')
- issue.custom_filter_spec = True
- print_supplement = models.ManyToManyField('issues.IssuePrintEditionSupplement', blank=True)
- print_supplement_section = models.ForeignKey('issues.IssuePrintEditionSupplementSection', blank=True, null=True)
- # Ex., Draft, Assigned, Published, etc.
- state = models.CharField(max_length=1, choices=constants.TITLE_STATE_CHOICES, default='D')
- # Online only
- web_only = models.BooleanField(default=False, help_text="The review can be online, but do not publish it in a magazine, even if someone selects an Issue for it.")
- # A Kirkus Star?
- star = models.BooleanField(default=False, help_text="Second only to The Prize as the greatest award in literature.")
- # Is it featured? Not sure how this is used, but likely it pulls it up to the Home Page
- featured_review = models.BooleanField("Recommended review", default=False, help_text="Not a Star, but you'd still recommend this book to a friend.")
- # Include this book in the list considered for The Prize
- prize_worthy = models.BooleanField(default=False, help_text="Mail this book to the judges!")
- # These are used in the CMS as list_display fields, but *are not* used to drive logic.
- # The values are rolled-up here so that we don't need extra DB queries when building
- # the listings.
- denorm_reviewer = models.CharField("qReviewer", editable=False, max_length=32, blank=True, null=True)
- denorm_publisher = models.CharField("qPublisher", editable=False, max_length=64, blank=True, null=True)
- denorm_issue_date = models.DateField("qIssue Date", editable=False, blank=True, null=True)
- # If the review was particularly negative, we might want to know so that we don't
- # push this person with Marketing outreach (like ad sales and Pro)
- is_negative_review = models.BooleanField("Ouch! Particulary bad review", default=False, help_text="Let's just say that the author might be upset with the review and we probably shouldn't send marketing outreach campaigns their way.")
- # Guessing this means whether it was a Kirkus Discovery submission
- discovery = models.BooleanField("Indie", default=False, help_text="This is an Indie review--ain't no shame in that, baby!")
- # How the manuscript was submitted for review.
- manuscript_submission_format = models.ForeignKey(ManuscriptSubmissionFormat, blank=True, null=True)
- # Reviews have four text fields to play with, though we really only use 'review'.
- abstract = models.TextField(blank=True)
- review = models.TextField(blank=True)
- aftermatter = models.TextField(blank=True)
- notes = models.TextField(blank=True)
- synopsis = models.TextField(blank=True)
- # Field for extra keywords/phrases that should be indexed for search.
- search_extra = models.TextField("Additional text for Search", blank=True, help_text="The text here will be indexed for searching, but will not be visible to the customer.")
- ################ Book-related ####################
- series = models.ForeignKey(Series, blank=True, null=True)
- title = models.CharField(max_length=255)
- subtitle = models.CharField(max_length=255, blank=True, help_text="Use the following formatting in Title or Subtitle fields:<br><b><b>bold</b></b><br><i><i>italic</i></i><br><u><u>underline</u></u>")
- volume = models.CharField(max_length=10, blank=True)
- author = models.ManyToManyField(Author, through='TitleAuthor', blank=True)
- publisher = models.ForeignKey(Publisher, blank=True, null=True)
- num_pages = models.IntegerField("Number of Pages", blank=True, null=True, validators=[MinValueValidator(0)])
- # These are demormalized fields set by signals off of MediaType and Author
- primary_isbn = models.CharField(max_length=20, blank=True, help_text="This is the MOST IMPORTANT ISBN field. It is published with the reivew, and used to lookup the cover from Amazon.")
- primary_author_slug = models.CharField(max_length=50, blank=True, help_text="This is used in the review\'s URL. It's *very* important. If you change this, you need to also setup a Redirect to map the old URL to the new one.")
- primary_author_first_name = models.CharField("Author First", max_length=100, blank=True)
- primary_author_last_name = models.CharField("Author Last", max_length=100, blank=True)
- primary_author_list = models.TextField("Authors", blank=True)
- # There's no Admin for Stage, but expect possible Strings like "Adult" or "Children's"
- # The annoying part of this is that various 'stage', 'category', and 'classification1'
- # queries are used to drive the front-end, top-level navigation results.
- stage = models.ForeignKey(Stage)
- category = ChainedForeignKey(Category, chained_field="stage", chained_model_field="stage", include_jquery=False, null=True, blank=True)
- classification1 = ChainedForeignKey(Classification1, chained_field="category", chained_model_field="category", include_jquery=False, null=True, blank=True)
- classification2 = ChainedForeignKey(Classification2, chained_field="classification1", chained_model_field="classification1", include_jquery=False, null=True, blank=True)
- classification3 = ChainedForeignKey(Classification3, chained_field="classification2", chained_model_field="classification2", include_jquery=False, null=True, blank=True)
- navigation_categories = models.ManyToManyField(NavigationCategory, blank=True, limit_choices_to={'parent__isnull': True}, help_text="These are very important. They define the sections of the site that you want this book to show up in. For Indie books, select the Indie category, but you MUST also select another relevant category.")
- navigation_formats = models.ManyToManyField(NavigationFormat, blank=True)
- # Age ranges
- lower_age = models.SmallIntegerField(blank=True, null=True, default=0)
- upper_age = models.SmallIntegerField(blank=True, null=True, default=0)
- # For ticket #553
- audience = models.SmallIntegerField(choices=constants.TITLE_AUDIENCE, blank=True, default=3)
- # When the book was/will-be published
- pub_date = models.DateField("Book Pub Date", blank=True, null=True)
- ################ End Book-related ####################
- received_date = models.DateField("Galley Rec'd", blank=True, null=True)
- # BKD: This is a bad related_name. Change to titles?
- reviewer = models.ForeignKey(User, blank=True, null=True, related_name='reviewer')
- assigned_time = models.DateTimeField(blank=True, null=True)
- assigned_by = models.ForeignKey(User, blank=True, null=True)
- date_assignment_acknowledged = models.DateTimeField("Assignment Acknowledged", blank=True, null=True)
- review_editor = models.ForeignKey(User, blank=True, null=True, related_name='editor', limit_choices_to={'groups__id': 1})
- # review_date is temporary
- review_date = models.DateTimeField(editable=False, blank=True, null=True)
- # review_deadline1 = models.DateField("First Deadline", blank=True, null=True, help_text='This field is deprecated, and hidden in the Admin')
- review_deadline2 = models.DateField("Reviewer Deadline", blank=True, null=True)
- review_type = models.ForeignKey(ReviewRate)
- # EBS: Does the new Invoice package deprecate these fields???!
- invoice_state = models.CharField(max_length=1, choices=constants.INVOICE_STATE_CHOICES, null=True, blank=True, editable=False)
- invoice_date = models.DateField("Invoice Date", blank=True, null=True)
- base_rate = models.DecimalField('Base Rate', max_digits=8, decimal_places=2, null=True, blank=True)
- addl_rate = models.DecimalField('Additional Pay', max_digits=8, decimal_places=2, null=True, blank=True, default='0.00', help_text="Wow--this book is book is long, yo.")
- total_rate = models.DecimalField('Total', max_digits=8, decimal_places=2, null=True, blank=True)
- # This was an actual data field in the Nielsen data:
- # old_title_id = models.IntegerField(blank=True, null=True, editable=False)
- is_archive = models.BooleanField(default=False)
- is_express_review = models.BooleanField("Express Review", default=False, help_text="A lot like the Pony Express, but with Indie books.")
- has_sent_clipping = models.BooleanField(default=False, blank=True, help_text="If this is checked, the system thinks it has already sent a clipping for this review, and it will not send another one.")
- has_sent_prize_book_request = models.BooleanField(default=False, blank=True, help_text="If this is checked, the system thinks it has already sent a book request for this review, and it will not send another one.")
- is_public = models.BooleanField("Marketing Promotion", default=False, help_text="Does things only the marketing department can understand.")
- roundup = models.BooleanField("Roundup", default=False, help_text="Giddy-up cowboy--we've got some reviews to roundup.")
- # available_rights = models.ManyToManyField('TitleRight', blank=True)
- ################ Affiliate URLs/links ####################
- # These are denorm caches, since we can look this up.
- amazon_affiliate_link = models.CharField(max_length=300, blank=True, null=True, help_text="We try auto-detecting this URL, but it doesn\'t always work for Kindle books. You might need to paste in a new URL for Kindle-only books.")
- amazon_affiliate_link_updated_date = models.DateTimeField(editable=False, null=True)
- amazon_isbn = models.CharField("IndieBound Buy Button ISBN", max_length=24, blank=True, null=True, help_text="This is what Amazon responded with as the ISBN they use.")
- amazon_ean = models.CharField("BN Buy/Button EAN", max_length=32, blank=True, null=True, help_text="This comes from Amazon, but we use it for the BN Buy button. Replace with a BN ID if you want to link to a Nook book.")
- # Potentially Deprecated, pending further development with BN API integration.
- # These fields are now hidden within the Admin since we rely on BN lookups now.
- # bn_affiliate_link = models.TextField(blank=True, null=True)
- # bn_affiliate_link_updated_date = models.DateTimeField(editable=False, null=True)
- # kobo_affiliate_link = models.TextField(blank=True, null=True)
- # kobo_affiliate_link_updated_date = models.DateTimeField(editable=False, null=True)
- ################ END Affiliate URLs/links ####################
- published_date = models.DateTimeField("Publish Online", null=True, blank=True, help_text="If the `State` is `Publish`, this date sets the time after which the review will be on the website. The review will not be online without this set!")
- created_date = models.DateField(editable=False)
- updated_date = models.DateTimeField(editable=False)
- ################ Custom Managers ####################
- objects = TitleManager()
- available_titles = AvailableTitleManager()
- ################ Instance Variables ####################
- _is_app_only = None
- _book_cover_object = None
- _best_seller_object = None
- _vintage_review_object = None
- _review_first_sentence = ''
- _review_middle_block = ''
- _review_last_sentence = ''
- _review_age_range_str = ''
- _review_parsed = ''
- _oliver_data = {}
- _bic_subjects = []
- _parenthetical_genres = []
- @classmethod
- def gen_title_slug(cls, title_string):
- # Check for duplicate slug. If one exists, we add some random characters
- # and cross our fingers.
- #
- # start with your standard-issue `slugify`
- slug = slugify(title_string)
- had_collision = False
- first_collision_slug = None
- def _slug_is_available(s):
- if Title.objects.filter(title_slug=slug).exists():
- return False
- else:
- return True
- is_available = _slug_is_available(slug)
- count = 0
- mode = 'letters'
- nums = '0123456789'
- while not is_available:
- count += 1
- had_collision = True
- if first_collision_slug is None:
- first_collision_slug = slug
- if mode == 'letters':
- # Append a random char
- slug = '{}{}'.format(slug, random.choice(string.letters))
- elif mode == 'numbers':
- # Append a random number
- slug = '{}{}'.format(slug, random.choice(nums))
- if count == 4 or len(slug) > 50: # we won't be able to store it in the DB
- # try something else
- if mode == 'letters':
- # Switch it
- mode = 'numbers'
- slug = ''.join(random.choice(nums) for i in xrange(8))
- else:
- break
- if count > 8:
- # This is too much
- break
- is_available = _slug_is_available(slug)
- if is_available:
- return slug, had_collision, first_collision_slug
- return None, had_collision, first_collision_slug
- @classmethod
- def get_stage_and_audience_for_string(cls, s):
- if s == 'children':
- the_stage = Stage.objects.get(id=2)
- the_audience = 0
- elif s == 'adult':
- the_stage = Stage.objects.get(id=1)
- the_audience = 3
- elif s == 'teen':
- the_stage = Stage.objects.get(id=2)
- the_audience = 4
- else: # Default to adult if we get something we don't understand
- the_stage = Stage.objects.get(id=1)
- the_audience = 3
- return the_stage, the_audience
- def denormalize_isbn(self):
- """
- Set the primary_isbn from the first MediaType with an ISBN.
- """
- mt_qs = self.mediatype_set.exclude(isbn='').order_by('type')[0:1]
- if mt_qs.count() == 1:
- self.primary_isbn = mt_qs.get().isbn
- # Added 11/6/12
- # Even though we're calling save() on `self`, this method can
- # be triggered during a delete(), which isn't what we want,
- # so ignore the exception if `self` isn't actually in the DB.
- try:
- self.save()
- except:
- pass
- @property
- def should_have_an_invoice(self):
- if self.reviewer and self.state in ('E', 'C', 'R', 'L', 'P'):
- return True
- else:
- return False
- @property
- def files(self):
- return TitleFileRelatedManager(self)
- @property
- def indie_review(self):
- if self.indie_reviews.all().count() == 1:
- return self.indie_reviews.all()[0]
- else:
- return None
- def disclosure_note(self):
- single_note = "Note: {last} is a freelance contributor to Kirkus."
- multi_note = "Note: {names} are freelance contributors to Kirkus."
- aid_set = []
- names = []
- for ta in self.titleauthor_set.all():
- if ta.author and ta.author.is_kirkus_contributor:
- if ta.author.last_name and ta.author.id not in aid_set:
- names.append(ta.author.last_name)
- aid_set.append(ta.author.id)
- if names:
- if len(names) > 2:
- return multi_note.format(names='{base} and {last}'.format(base=', '.join(names[:-1]), last=names[-1]))
- elif len(names) == 2:
- return multi_note.format(names=' and '.join(names))
- elif len(names) == 1:
- return single_note.format(last=names[0])
- else:
- return None
- def save(self):
- # Grab a previous version from the DB (if it exists) for comparison
- prev_version = None
- if self.id:
- try:
- prev_version = Title.objects.get(id=self.id)
- except Title.DoesNotExist:
- pass
- # Added 2015-01-09
- # Truncate the slug if we're a new record and the slug is too long
- if ((prev_version is None) and (len(self.title_slug) > 50)):
- self.title_slug = self.title_slug[0:50]
- # We now track the date the review was submitted:
- if not self.review_date and self.review:
- self.review_date = datetime.now()
- # If someone sets this Title to publish but didn't set the
- # date for when to go live, assume they meant right now.
- if not self.published_date and self.state == 'P':
- self.published_date = datetime.now()
- # Lame crap because the original model didn't use auto date-stamps
- if not self.id:
- self.created_date = date.today()
- self.updated_date = datetime.now()
- # Added 2013-02-28
- if self.stage:
- # Added 2012-11-13
- # If our 'stage' is Children's, but our 'audience' is Adult..
- if self.stage.id == 2 and self.audience == 3:
- # ...set the audience to Children.
- self.audience = 0
- # Likewise, if our stage is Adult, but the audience is Children...
- elif self.stage.id == 1 and self.audience == 0:
- # ...set the audience to Adult.
- self.audience = 3
- # Added 9/5/12
- if self.addl_rate:
- self.addl_rate = extract.price_like(str(self.addl_rate))
- # Added 8/13/12
- # If the reivew has been assigned...
- if self.reviewer and self.state == 'A':
- # Set a due-date if missing
- if self.review_deadline2 is None:
- self.review_deadline2 = self.suggest_review_deadline()
- # Fill-in missing assignment-times
- if self.assigned_time is None:
- self.assigned_time = datetime.now()
- #
- if settings.USE_ZMQ_WORKERS:
- # We'll be sending background-work requests...
- work_pusher = pusher.Pusher(ip=settings.ZMQ_IP_ADDRESS, port=settings.ZMQ_PORT_WORKER)
- task_data = {"id": self.id, "model": "Title", "package": "kirkus.titles.models"}
- if self.is_published():
- task_data["action"] = "call"
- task_data["method"] = "put_in_search_index"
- else:
- task_data["action"] = "call"
- task_data["method"] = "remove_from_search_index"
- work_pusher.send_json(task_data)
- else:
- # Make it (new) searchable
- if self.is_published():
- try:
- self.put_in_search_index()
- except Exception: # likely a NoServerAvailable error
- # Don't take the system down for this!
- pass
- else:
- # Remove it from the index
- try:
- self.remove_from_search_index()
- except Exception: # likely a NoServerAvailable error
- # Don't take the system down for this!
- pass
- # Added 2014-6-23
- # Denorm a few properties for list_display usage
- if self.reviewer:
- self.denorm_reviewer = self.reviewer.username
- if self.publisher:
- self.denorm_publisher = self.publisher.publisher_name
- if self.issue:
- self.denorm_issue_date = self.issue.issue_date
- # Clean it everytime
- if self.synopsis:
- self.synopsis = fmt.full_html_strip(self.synopsis)
- #
- if prev_version is not None:
- old_path = prev_version.get_absolute_url()
- the_site = Site.objects.get_current()
- # If this review was just un-published...
- if self.state in ('U', 'Z', 'T') and prev_version.state == 'P':
- # ..we need to setup a Redirect
- try:
- r = Redirect.objects.get(site=the_site, old_path=old_path)
- except Redirect.DoesNotExist:
- r = Redirect(site=the_site, old_path=old_path, new_path='/')
- else:
- r.new_path = '/'
- r.save()
- # Or if this review was just resurected..
- elif self.state == 'P' and prev_version.state in ('U', 'Z', 'T'):
- # ..we need to delete any matching Redirects
- Redirect.objects.filter(site=the_site, old_path=old_path).delete()
- # Save it!
- super(Title, self).save()
- def __unicode__(self):
- return u'{s}'.format(s=self.title)
- def citation_date(self):
- if self.published_date and self.published_date.year > 2011:
- return self.published_date
- else:
- if self.issue and self.issue.issue_date:
- return self.issue.issue_date
- else:
- return None
- def get_synopsis(self):
- """
- Returns the synopsis.
- If there is no synopsis, ask oliver for one.
- """
- if not self.synopsis:
- oliver_data = self.oliver_data()
- synopsis = oliver_data.get('google_description', None)
- if synopsis:
- self.synopsis = fmt.full_html_strip(synopsis)
- try:
- self.save()
- except:
- pass
- return self.synopsis
- def get_updated_field(self):
- return 'updated_date'
- def review_stripped(self):
- stripped = removetags(self.review, 'p')
- return stripped
- @cached_property
- def pro_page_url(self):
- qs = self.titleauthor_set.all().select_related('author')
- for ta in qs:
- if ta.author and ta.author.is_pro_published and ta.author.is_pro:
- return ta.author.get_absolute_url()
- return None
- @cached_property
- def get_pro_author(self):
- """
- Gets the first Pro author. EBS: This logic fails for books with multiple Pro authors.
- """
- qs = self.titleauthor_set.all().select_related('author')
- for ta in qs:
- if ta.author and ta.author.is_pro_published and ta.author.is_pro:
- return ta.author
- return None
- def get_selected_pro_genres(self):
- return [atga.pro_genre for atga in self.authortitlegenreassignment_set.all()]
- def authors(self):
- return self.primary_author_list.replace('\n', '<br>')
- authors.allow_tags = True
- authors.admin_order_field = 'primary_author_last_name'
- def primary_author_obj(self):
- qs = self.titleauthor_set.all()
- try:
- return qs[0].author
- except:
- return None
- def primary_author(self):
- rtn = ""
- sep = ""
- if self.primary_author_last_name:
- rtn += self.primary_author_last_name
- sep = ", "
- if self.primary_author_first_name:
- rtn += sep + self.primary_author_first_name
- return rtn
- primary_author.admin_order_field = 'primary_author_last_name'
- def primary_author_full_name(self):
- qs = self.titleauthor_set.all()
- try:
- return u'{name}'.format(name=qs[0].author.full_name())
- except:
- return u'{first} {last}'.format(first=self.primary_author_first_name, last=self.primary_author_last_name)
- def secondary_author(self):
- qs = self.titleauthor_set.all()
- try:
- return qs[1].author
- except:
- return None
- @property
- def secondary_author_first_name(self):
- sa = self.secondary_author()
- if sa:
- return u'{first}'.format(first=sa.first_name)
- @property
- def secondary_author_last_name(self):
- sa = self.secondary_author()
- if sa:
- return u'{last}'.format(last=sa.last_name)
- def awards(self):
- results = []
- if self.star:
- results.append(BookAward(award=1, title=self))
- # Fake a Best-of award
- sup = self.get_best_of_supplement()
- if sup:
- results.append(BookAward(award=2, title=self, award_year=sup.date_published_online.year))
- results.extend(self.bookaward_set.exclude(award__in=(1, 2)))
- return results
- def stagecat(self):
- if self.discovery == True:
- return "Indie"
- elif self.stage_id == 2:
- return "Children"
- elif self.stage_id == 1 and self.classification1_id == 8:
- return "Mystery"
- elif self.stage_id == 1 and self.classification1_id == 10:
- return "Science Fiction"
- elif self.stage_id == 1 and self.category_id == 1:
- return "Fiction"
- elif self.stage_id == 1 and self.category_id == 2:
- return "Nonfiction"
- else:
- return "Adult"
- def stagecat_slug(self):
- if self.discovery == True:
- return "indie"
- elif self.stage_id == 2:
- return "childrens-books"
- elif self.stage_id == 1 and self.classification1_id == 8:
- return "mystery"
- elif self.stage_id == 1 and self.classification1_id == 10:
- return "science-fiction"
- elif self.stage_id == 1 and self.category_id == 1:
- return "fiction"
- elif self.stage_id == 1 and self.category_id == 2:
- return "non-fiction"
- elif self.category_id > 0:
- tmp = u'%s' % (self.category)
- return slugify(tmp).lower()
- else:
- return "adult"
- def print_section(self):
- if self.stage_id == 2:
- return "Children"
- elif self.stage_id == 1 and self.category_id == 1:
- return "Fiction"
- elif self.stage_id == 1 and self.category_id == 2:
- return "Nonfiction"
- else:
- return "Adult"
- def web_genre(self):
- qs = self.nav_sections()
- if qs.count() > 0:
- return qs[0].name
- else:
- return None
- def web_section(self, allow_indie=True):
- if self.discovery and allow_indie:
- return "Indie"
- elif self.stage_id == 2:
- return "Children"
- elif self.stage_id == 1 and self.classification1_id == 8:
- return "Mystery"
- elif self.stage_id == 1 and self.classification1_id == 10:
- return "Sci-Fi"
- elif self.stage_id == 1 and self.category_id == 1:
- return "Fiction"
- elif self.stage_id == 1 and self.category_id == 2:
- return "Nonfiction"
- else:
- return "Adult"
- def print_subsection(self):
- if self.stage_id == 1 and self.classification1_id == 8:
- return "Mystery"
- elif self.stage_id == 1 and self.classification1_id == 10:
- return "Science Fiction"
- elif self.category_id > 0:
- tmp = u'%s' % (self.category)
- return tmp
- else:
- return ""
- def nav_sections(self):
- return self.navigation_categories.filter(active=True, display_in_books_nav=True).exclude(navcat_slug="indie")
- def main_nav_sections(self):
- return self.nav_sections().filter(parent__isnull=True)
- def best_date(self):
- if self.pub_date:
- return str(self.pub_date)
- else:
- return ""
- def pub_date_state(self):
- # this is used te determine how much of teh review to show
- # 0 = more than 14 days until pub_date
- # 1 = less than 14 days until pub_date
- # 2 = pub_date has passed
- pds = 2
- if self.pub_date:
- pub_yr = self.pub_date.year
- pub_mo = self.pub_date.month
- pub_dy = self.pub_date.day
- days_to_pub = days_diff(pub_yr, pub_mo, pub_dy)
- if days_to_pub > 14:
- pds = 0
- elif days_to_pub > 0:
- pds = 1
- else:
- pds = 2
- return pds
- def public_review(self):
- """DEPRECATED"""
- pr = False
- pds = self.pub_date_state()
- if self.is_public or pds > 0:
- pr = True
- return pr
- @cached_property
- def best_seller_object(self):
- if self._best_seller_object is None:
- try:
- self._best_seller_object = BestSeller.objects.get(title=self)
- except BestSeller.DoesNotExist:
- pass
- except MultipleObjectsReturned:
- self._best_seller_object = BestSeller.objects.filter(title=self)[0]
- return self._best_seller_object
- @property
- def vintage_review_object(self):
- now = datetime.now()
- if self._vintage_review_object is None:
- try:
- self._vintage_review_object = VintageReview.objects.get(title=self, published_date__lte=now)
- except VintageReview.DoesNotExist:
- pass
- return self._vintage_review_object
- def is_published(self):
- now = datetime.now()
- if (self.published_date and self.published_date <= now) and (self.state == 'P'):
- return True
- else:
- return False
- def is_app_only(self):
- """
- Return boolean indicated whether this Title only has MediaType(s) of type App.
- """
- # We cache the result since we're likely to be called multiple times from the front-end code
- if self._is_app_only is not None:
- return self._is_app_only
- found_an_app = False
- found_a_non_app_type = False
- if self.category and self.category.category_name == 'iPad':
- found_an_app = True
- else:
- for mt in self.mediatype_set.all():
- if mt.type == 7:
- found_an_app = True
- else:
- found_a_non_app_type = True
- self._is_app_only = found_an_app and not found_a_non_app_type
- return self._is_app_only
- def isbns(self):
- """
- Return a list of all ISBNs we know about for this Title.
- """
- results = []
- if self.primary_isbn:
- results.append(fmt.super_flat(self.primary_isbn))
- for mt in self.mediatype_set.all():
- if mt.isbn:
- the_isbn = fmt.super_flat(mt.isbn)
- if the_isbn not in results:
- results.append(the_isbn)
- return results
- def isbn10(self):
- """Return the first 10-char ISBN we have."""
- for i in self.isbns():
- if len(i) == 10:
- return i
- return ''
- def isbn13(self):
- """Return the first 13-char ISBN we have."""
- for i in self.isbns():
- if len(i) == 13:
- return i
- return ''
- def get_absolute_url(self):
- author_slug = self.primary_author_slug if self.primary_author_slug else 'unknown'
- url = u"/book-reviews/{author_slug}/{title_slug}/".format(author_slug=urlquote(author_slug), title_slug=urlquote(self.title_slug))
- return iri_to_uri(url)
- def similar_titles(self, published_only=True):
- """
- Return a list of Titles that are recommended as similar, and that have had
- this book listed as being similar.
- This call makes a few DB queries and iterates over the results. Best to cache it.
- """
- # Books listed as similars to this title
- similar_set = self.similartitle_set.all()
- # Books that list this book as similar
- similar_to_set = self.similar.all()
- results = []
- ids = [] # Used to track uniqueness
- for st in similar_set:
- if st.similar and st.similar.id not in ids:
- if published_only and not st.similar.is_published():
- continue
- results.append(st.similar)
- ids.append(st.similar.id)
- for st in similar_to_set:
- if st.title.id not in ids:
- if published_only and not st.title.is_published():
- continue
- results.append(st.title)
- ids.append(st.title.id)
- # Shuffle the order...
- random.shuffle(results)
- return results
- @cached_property
- def app_cover_art(self):
- """
- Return all MediaTypeAssets (for all my MediaTypes) that contain cover-shots
- """
- r = []
- for mt in self.mediatype_set.all():
- for mta in mt.mediatypeasset_set.all():
- if (mta.asset_type == 1) and mta.is_cover:
- r.append(mta)
- return r
- @cached_property
- def screen_shots(self):
- """
- Return all MediaTypeAssets (for all my MediaTypes) that contain screen-shots
- # EBS: This is a good candidate for caching, since the review template calls it a couple times.
- """
- r = []
- for mt in self.mediatype_set.all():
- for mta in mt.mediatypeasset_set.all():
- if (mta.asset_type == 1) and mta.is_screenshot:
- r.append(mta)
- return r
- @cached_property
- def videos(self):
- """
- Return all MediaTypeAssets (for all my MediaTypes) that contain video embed code.
- # EBS: This is a good candidate for caching, since the review template calls it a couple times.
- """
- r = []
- for mt in self.mediatype_set.all():
- for mta in mt.mediatypeasset_set.all():
- if (mta.asset_type == 2) and (mta.embed_code is not None):
- r.append(mta)
- return r
- def age_range_str(self):
- if not self._review_age_range_str:
- # Then we need to build one
- # First, let's look at our stored values
- lower = self.lower_age
- upper = self.upper_age
- if ((lower is None) and (upper is None)) or ((lower == 0) and (upper == 0)):
- # Then we need new values
- rp = ReviewParser()
- rp.parse(self.review)
- self._review_first_sentence = rp.first_sentence
- self._review_middle_block = rp.middle_block
- self._review_last_sentence = rp.last_sentence
- lower, upper = rp.extract_age_range()
- # Save these for next time
- self.lower_age = lower
- self.upper_age = upper
- try:
- self.save()
- except:
- pass
- if (lower is not None and upper is not None) and (upper > lower):
- self._review_age_range_str = '{l} - {u}'.format(l=lower, u=upper)
- elif lower is not None and upper is None:
- self._review_age_range_str = '{l} & up'.format(l=lower)
- else:
- self._review_age_range_str = None
- return self._review_age_range_str
- def review_first_sentence(self):
- if not self._review_first_sentence:
- rp = ReviewParser()
- rp.parse(self.review)
- self._review_first_sentence = rp.first_sentence
- self._review_middle_block = rp.middle_block
- self._review_last_sentence = rp.last_sentence
- self._review_parsed = rp.content
- # lower, upper = rp.extract_age_range()
- # if lower is not None and upper is not None:
- # self._review_age_range_str = '{l} - {u}'.format(l=lower, u=upper)
- # else:
- # self._review_age_range_str = None
- return self._review_first_sentence
- def review_middle_block(self):
- if not self._review_middle_block:
- rp = ReviewParser()
- rp.parse(self.review)
- self._review_first_sentence = rp.first_sentence
- self._review_middle_block = rp.middle_block
- self._review_last_sentence = rp.last_sentence
- self._review_parsed = rp.content
- # lower, upper = rp.extract_age_range()
- # if lower is not None and upper is not None:
- # self._review_age_range_str = '{l} - {u}'.format(l=lower, u=upper)
- # else:
- # self._review_age_range_str = None
- return self._review_middle_block
- def review_last_sentence(self):
- if not self._review_last_sentence:
- rp = ReviewParser()
- rp.parse(self.review)
- self._review_first_sentence = rp.first_sentence
- self._review_middle_block = rp.middle_block
- self._review_last_sentence = rp.last_sentence
- self._review_parsed = rp.content
- # lower, upper = rp.extract_age_range()
- # if lower is not None and upper is not None:
- # self._review_age_range_str = '{l} - {u}'.format(l=lower, u=upper)
- # else:
- # self._review_age_range_str = None
- return self._review_last_sentence
- def review_parsed(self):
- if not self._review_parsed:
- rp = ReviewParser()
- rp.parse(self.review)
- self._review_first_sentence = rp.first_sentence
- self._review_middle_block = rp.middle_block
- self._review_last_sentence = rp.last_sentence
- self._review_parsed = rp.content
- # lower, upper = rp.extract_age_range()
- # if lower is not None and upper is not None:
- # self._review_age_range_str = '{l} - {u}'.format(l=lower, u=upper)
- # else:
- # self._review_age_range_str = None
- return self._review_parsed
- def review_last_sentence_is_usable(self):
- """
- Replaces template logic that looked for a last_sentence that was between 3 and 300 chars.
- Also allows customizing the logic to add exceptions for some long reviews.
- """
- s = self.review_last_sentence()
- if not s:
- return False
- # The easy test:
- if 3 < len(s) < 300:
- return True
- else:
- # Allow a wider range for newer reviews
- if self.published_date and self.published_date.year >= 2011:
- if 3 < len(s) < 500:
- return True
- return False
- def has_primary_isbn(self):
- return bool(self.primary_isbn)
- has_primary_isbn.boolean = True
- has_primary_isbn.short_description = "ISBN"
- def cover_image_preview_url(self):
- return self.cover_image_url('180', mode='s3_direct_url')
- def cover_image_preview_urls(self):
- urls = []
- for width in settings.COVER_WIDTHS:
- urls.append({'width': width, 'url': self.cover_image_url(width, mode='s3_direct_url')})
- return urls
- def has_cover_by_size(self, width, check_disk=True):
- """
- Used by: util/printout/gen_issue_xtags.py
- """
- result = False
- # If this is an app, get the MediaType cover.
- mta_covers = self.app_cover_art
- if mta_covers or self.is_app_only():
- try:
- return '{base}{path}'.format(base=settings.STATIC_ROOT, path=mta_covers[0].image)
- except IndexError:
- return '{base}/{w}/default.png'.format(base=settings.COVERS_ROOT, w=width)
- else:
- bc = self.book_cover_object
- result = bc.has_cover_by_size(width=width, check_disk=check_disk)
- return result
- def cover_image_url(self, width, mode='s3_origin'):
- if self.is_app_only():
- mta_covers = self.app_cover_art
- try:
- path = "{base}{img}".format(base=settings.STATIC_URL, img=mta_covers[0].image)
- except IndexError:
- path = '{base}{size}/default.png'.format(base=settings.COVERS_URL, size=width)
- else:
- try:
- path = self.book_cover_object.cover_image_url(width=width, mode=mode)
- except AttributeError:
- path = '{base}{size}/default.png'.format(base=settings.COVERS_URL, size=width)
- return path
- @property
- def discovery_transaction(self):
- """
- Return a related transaction, if it exists.
- """
- from discoveries.models import IndieReview
- transaction_qs = IndieReview.objects.filter(title__id=self.id)
- if transaction_qs.count() > 0:
- return transaction_qs[0]
- return None
- def get_prefered_affiliate_dict(self):
- d = dict()
- d['amazon_link'] = self.amazon_affiliate_link
- if self.primary_isbn or d['amazon_link']:
- d['store'] = ''
- d['store_display'] = ''
- d['type'] = ''
- d['platform'] = ''
- # If we have a cached/denormed copy of the affiliate link, use that
- #d['amazon_link'] = self.amazon_affiliate_link
- d['isbn'] = self.amazon_isbn
- d['ean'] = self.amazon_ean
- if d['amazon_link']:
- d['store'] = 'amazon'
- d['store_display'] = 'Amazon'
- # If we didn't get an amazon_link, ask Oliver for it
- # if d['amazon_link'] and d['ean']:
- # pass
- else:
- clean_isbn = self.primary_isbn.upper().replace('-', '').replace(' ', '')
- # If there's no ISBN, don't try to fetch Oliver data
- if len(clean_isbn) > 5:
- save_title = False # Flag used to store whether we found new denorm data to cache
- o = Oliver()
- meta_data = o.detail_by_isbn(isbn=clean_isbn)
- #link = o.property_by_isbn(isbn=clean_isbn, property='amazon_detail_url', provider='amazon')
- try:
- d['amazon_link'] = meta_data['amazon_detail_url']
- except KeyError:
- pass
- else:
- if d['amazon_link']:
- d['store'] = 'amazon'
- d['store_display'] = 'Amazon'
- # Save a copy
- self.amazon_affiliate_link = d['amazon_link']
- self.amazon_affiliate_link_updated_date = datetime.now()
- save_title = True
- if not d['ean']:
- try:
- d['ean'] = meta_data['amazon_ean']
- except KeyError:
- d['ean'] = None
- else:
- self.amazon_ean = d['ean']
- save_title = True
- if not d['isbn']:
- try:
- d['isbn'] = meta_data['amazon_isbn']
- except KeyError:
- d['isbn'] = None
- else:
- self.amazon_isbn = d['isbn']
- save_title = True
- if save_title:
- try:
- self.save()
- except ValueError:
- # Dang. We've gotten a ValueError in the past with bunk affiliate data.
- pass
- if d['ean'] and not(d['isbn']) and not d['store']:
- d['store'] = 'bn'
- d['store_display'] = 'Barnes & Noble'
- return d
- else:
- matches = {}
- for mt in self.mediatype_set.all():
- d = mt.affiliate_buy_data()
- # Apple trumps all
- if d['store'] == 'apple':
- return d
- if d['store'] is not None:
- matches[d['type'].lower()] = d
- # If no Apple Store, try the paperback, then the hardback
- try:
- return matches['paperback']
- except KeyError:
- try:
- return matches['hardcover']
- except KeyError:
- return None
- def book_price(self, type='Hardcover'):
- last_price = ''
- for mt in self.mediatype_set.all():
- if str(mt.get_type_display()) == str(type) and mt.price:
- return str(mt.price)
- else:
- if mt.price:
- last_price = str(mt.price)
- return last_price
- @property
- def invoiced_item(self):
- """
- Return the first invoiced item associated with this title.
- """
- invoiced_items = self.invoiced_items
- if invoiced_items.count() > 0:
- return invoiced_items[0]
- return None
- @property
- def invoiced_items(self):
- """
- Return a QuerySet of InvoicedItem objects associated with this title.
- """
- title_content_type = ContentType.objects.get_for_model(Title)
- return InvoicedItem.objects.filter(content_object_id=self.id, content_type=title_content_type)
- def has_invoice(self):
- if self.invoiced_item is not None:
- return True
- else:
- return False
- has_invoice.boolean = True
- def get_review_payable_price(self, mode='total', user_to_invoice=None):
- amount = 0.0
- if user_to_invoice is None:
- user_to_invoice = self.reviewer
- try:
- # Look for a UserReviewRate for the assigned reviewer
- from profiles.models import UserReviewRate
- amount = float(UserReviewRate.objects.filter(user=user_to_invoice, review_type=self.review_type)[0].user_rate)
- except:
- amount = float(self.review_type.default_rate)
- if mode == 'total':
- try:
- amount += float(self.addl_rate)
- except:
- pass
- return amount
- def is_confirmed(self):
- if self.date_assignment_acknowledged is not None:
- return True
- else:
- return False
- is_confirmed.boolean = True
- is_confirmed.short_description = 'Confirmed'
- @property
- def user_to_invoice(self):
- return self.reviewer
- def oliver_data(self, verbose=False):
- """
- Returns dictionary of data from Oliver for the book.
- """
- if not self._oliver_data:
- oliver = Oliver()
- oliver_data = oliver.detail_by_isbn(self.primary_isbn.strip())
- if len(oliver_data) == 0:
- if verbose:
- print 'NO OLIVER DATA!', oliver_data
- self._oliver_data = oliver_data
- return self._oliver_data
- def bic_subjects(self, verbose=False):
- """
- Returns a list of BIC subjects for a book.
- """
- if not self._bic_subjects:
- bic_subjects = []
- if self.primary_isbn:
- # Get book data from Oliver.
- oliver_data = self.oliver_data(verbose=verbose)
- bic_subjects_string = oliver_data.get('bowker_bicsubject', '')
- bic_subjects_string = force_latin1(unescapeHTML(bic_subjects_string))
- # Clean subjects and split into a list.
- bic_subjects_string = bic_subjects_string.replace('&', '&')
- bic_subject_names = bic_subjects_string.split(';')
- for bic_subject_name in bic_subject_names:
- bic_subject_name = bic_subject_name.strip()
- if len(bic_subject_name) > 0:
- bic_subject = get_or_create_bic_subject(bic_subject_name)
- bic_subjects.append(bic_subject)
- self._bic_subjects = bic_subjects
- return self._bic_subjects
- def parenthetical_genres(self):
- """
- Returns a list of genres parsed from the last sentence of the review.
- """
- if not self._parenthetical_genres:
- genres = []
- parser = ReviewParser()
- parser.parse(self.review)
- extracted_genre = parser.extract_genre()
- if extracted_genre:
- for genre in extracted_genre.split('/'):
- if len(genre) > 0:
- genre = get_or_create_parenthetical_genre(genre)
- genres.append(genre)
- self._parenthetical_genres = genres
- return self._parenthetical_genres
- def update_navigation_categories(self, overwrite=False, modify=False, verbose=False):
- """
- Updates the Title's navigation categories based on bowker_bicsubject and classification/navigation category relations.
- """
- if overwrite:
- self.navigation_categories = []
- self.navigation_formats = []
- navigation_categories_count = self.navigation_categories.count()
- if navigation_categories_count == 0 or (modify and navigation_categories_count > 0):
- # Add all navigation categories/formats related to this BIC subject, with the same Stage/Category.
- for bic_subject in self.bic_subjects(verbose=verbose):
- #for navigation_category in bic_subject.navigation_categories.filter(stage_category=self.category):
- for navigation_category in bic_subject.navigation_categories.filter(stage_category__stage=self.stage):
- self.navigation_categories.add(navigation_category)
- for navigation_format in bic_subject.navigation_formats.filter(stage_category__stage=self.stage):
- self.navigation_formats.add(navigation_format)
- if self.category:
- for navigation_category in self.category.navigation_categories.all():
- self.navigation_categories.add(navigation_category)
- for navigation_format in self.category.navigation_formats.all():
- self.navigation_formats.add(navigation_format)
- # Add navigation categories by classification.
- for classification_name in ('classification1', 'classification2', 'classification3'):
- try:
- title_classification = getattr(self, classification_name, None)
- if title_classification:
- for navigation_category in title_classification.navigation_categories.filter(stage_category=self.category):
- self.navigation_categories.add(navigation_category)
- for navigation_format in title_classification.navigation_formats.all():
- self.navigation_formats.add(navigation_format)
- except ObjectDoesNotExist:
- # The Classification object does not exist.
- # For some reason, there are lots of classification1_id fields set to 0, where there is no Classification1 with an id of 0.
- pass
- self.save()
- def prize_entry_obj(self):
- obj = None
- try:
- obj = self.kirkusprizeentry_set.all()[0]
- except IndexError:
- pass
- return obj
- def is_prize_finalist(self):
- return self.kirkusprizeentry_set.filter(is_finalist=True).exists()
- def is_prize_winner(self):
- return self.kirkusprizeentry_set.filter(is_winner=True).exists()
- def prize_year_eligible(self):
- """
- If this book is/was eligible to be considered for the Kirkus Prize, return the year's prize it
- was eligible for. Otherwise, return 0.
- """
- # Has to be a star
- # Has to be published
- # And we didn't start until 2013 books
- if self.star and self.state == 'P' and self.pub_date and self.published_date and self.published_date.year > 2012:
- # No apps
- if self.is_app_only():
- return 0
- # Adult
- if self.stage == 1:
- start_date, end_date = TitleManager.kirkus_prize_date_range(year=self.pub_date.year)
- if start_date <= self.pub_date <= end_date:
- return self.pub_date.year
- # YA
- elif self.stage == 2:
- start_date, end_date = TitleManager.kirkus_prize_date_range(year=self.pub_date.year, section='young-readers')
- if start_date <= self.pub_date <= end_date:
- return self.pub_date.year
- # Indie
- elif self.stage == 4:
- start_date, end_date = TitleManager.kirkus_prize_date_range(year=self.published_date.year, section='indie')
- if start_date <= self.published_date <= end_date:
- return self.published_date.year
- else:
- start_date, end_date = TitleManager.kirkus_prize_date_range(year=self.pub_date.year)
- if start_date <= self.pub_date <= end_date:
- return self.pub_date.year
- return 0
- @classmethod
- def index_available_items(cls, limit=0, delay=0, verbose=False, very_verbose=False):
- count = 0
- qs = Title.available_titles.all()
- if limit > 0:
- qs = qs[0:limit]
- for t in qs:
- if verbose:
- try:
- print("[{c}] <{id}> {title}".format(c=count, id=t.id, title=t.title))
- except UnicodeEncodeError:
- print("ERR: <{id}>".format(id=t.id))
- t.put_in_search_index(verbose=very_verbose)
- count += 1
- if delay:
- time.sleep(delay)
- def put_in_search_index(self, verbose=False):
- d = dict()
- if verbose:
- print(u'Indexing Title<{id}>: {title}'.format(id=self.id, title=self.title))
- #
- # Load cluster data from Oliver...
- if self.primary_isbn:
- o = Oliver()
- meta_data = o.detail_by_isbn(isbn=self.primary_isbn)
- else:
- o = None
- #
- d['audience'] = self.get_audience_display().lower()
- d['authors'] = [ta.author.full_name().replace('*', '').replace('"', '').replace("'", '') for ta in self.titleauthor_set.all()]
- try:
- d['category'] = slugify(self.category.category_name)
- except AttributeError:
- d['category'] = ''
- if o is not None:
- try:
- # De-slugify for searching
- d['clusters'] = [c.replace('-', ' ') for c in meta_data['oliver_subject_terms']]
- except KeyError:
- d['clusters'] = []
- else:
- d['clusters'] = []
- d['id'] = str(self.id)
- d['is_indie'] = self.discovery
- d['is_recommended'] = self.featured_review
- d['is_star'] = self.star
- d['is_app'] = self.is_app_only()
- d['prize_eligible_year'] = self.prize_year_eligible()
- d['isbn'] = self.isbns()
- try:
- d['last_modified'] = self.updated_date.strftime('%Y-%m-%d %H:%M:%S')
- except AttributeError:
- pass
- try:
- d['issue_date'] = self.issue.issue_date.strftime('%Y-%m-%d')
- except AttributeError:
- pass
- try:
- d['navigation_categories'] = [slugify(nc.name) for nc in self.navigation_categories.all()]
- except Exception:
- d['navigation_categories'] = ''
- d['page_count'] = self.num_pages
- try:
- d['pub_date'] = self.pub_date.strftime('%Y-%m-%d')
- except AttributeError:
- pass
- try:
- d['publisher'] = self.publisher.get_publisher_inheritance_list(verbose=verbose)
- except AttributeError:
- d['publisher'] = ''
- d['reader_age_min'] = self.lower_age
- d['reader_age_max'] = self.upper_age
- try:
- d['review'] = fmt.strip_tags(smart_str(self.review))
- except UnicodeEncodeError:
- pass
- try:
- d['search_extra'] = fmt.strip_tags(smart_str(self.search_extra))
- if d['search_extra'] == 'None' or d['search_extra'] == None:
- d['search_extra'] = ''
- except Exception:
- d['search_extra'] = ''
- d['series'] = self.series.series_name if self.series else ''
- d['series_position'] = self.volume
- d['slug'] = self.title_slug
- try:
- d['stage'] = slugify(self.stage.stage_name)
- except AttributeError:
- d['stage'] = ''
- d['subtitle'] = self.subtitle
- d['title'] = self.title
- d['type'] = 'title'
- #
- es_search_util.put_d_in_index(index='reviews', mapping='review', d=d, id=d['id'], verbose=verbose)
- def remove_from_search_index(self, verbose=False):
- es_search_util.remove_from_index(index='reviews', mapping='review', id=self.id, verbose=verbose)
- def suggest_review_deadline(self):
- policy = get_business_dates_policy()
- today = date.today()
- deadline = None
- if self.discovery:
- if self.manuscript_submission_format and self.manuscript_submission_format.name == 'digital':
- deadline = today + timedelta(weeks=2)
- else:
- deadline = today + timedelta(weeks=2, days=2)
- deadline = policy.closest_biz_day(deadline, forward=True)
- return deadline
- def get_book_cover_key(self):
- key = None
- if self.primary_isbn:
- key = 'isbn:{isbn}'.format(isbn=self.primary_isbn)
- else:
- key = 'kr:{id}'.format(id=self.id)
- return key
- @property
- def book_cover_object(self):
- if self._book_cover_object is not None:
- return self._book_cover_object
- else:
- if self.is_app_only():
- self._book_cover_object = AppCover.get_or_create(key=self.get_book_cover_key())
- # get_or_create returns a BookCover, so we need to tell the
- # interpretor that we want an AppCover instead:
- self._book_cover_object.__class__ = AppCover
- else:
- self._book_cover_object = BookCover.get_or_create(key=self.get_book_cover_key())
- if self._book_cover_object is not None:
- self._book_cover_object.parent = self
- return self._book_cover_object
- return None
- def best_of_2013_schedule_slug(self):
- return self.print_supplement_schedule_slug()
- def print_supplement_schedule_slug(self):
- if self.print_supplement_section:
- if self.print_supplement_section.slug == 'apps':
- return 'book-apps'
- elif self.print_supplement_section.slug in ('children', 'childrens'):
- return 'children'
- elif self.print_supplement_section.slug == 'fiction':
- return 'fiction'
- elif self.print_supplement_section.slug == 'indie':
- return 'indie'
- elif self.print_supplement_section.slug in ('middle-grade', 'middle-grader', 'middle-graders'):
- if self.pub_date.year == 2015:
- return 'middle-grade'
- else:
- return 'children'
- elif self.print_supplement_section.slug == 'nonfiction':
- return 'nonfiction'
- elif self.print_supplement_section.slug == 'picture-books':
- if self.pub_date.year == 2015:
- return 'picture-books'
- else:
- return 'children'
- elif self.print_supplement_section.slug == 'teen':
- return 'teen'
- else:
- return ''
- elif self.discovery:
- return 'indie'
- elif self.is_app_only():
- return 'book-apps'
- elif self.category and self.category.category_name == 'Fiction':
- return 'fiction'
- elif self.category and self.category.category_name == 'Nonfiction':
- return 'nonfiction'
- elif self.stage and self.stage.stage_name == "Children's":
- return 'children'
- else:
- return ''
- def is_in_supplement(self, slug):
- """
- Returns the supplement, if found. False otherwise.
- """
- try:
- return self.print_supplement.get(slug=slug)
- except:
- return False
- @cached_property
- def is_best_of_2015(self):
- """Checking on the current year is more complicated, since we want to ensure that we
- don't leak the winners in sections that aren't live yet.
- """
- from issues.models import IssuePrintEditionSupplementSchedule
- sup = self.is_in_supplement(slug='best-of-2015')
- if sup and sup.is_published():
- # Find out what section it's in
- schedule_slug = self.print_supplement_schedule_slug()
- if schedule_slug: # Which could the an empty string if this Title doesn't have one
- try:
- sched = IssuePrintEditionSupplementSchedule.objects.get(supplement=sup, nav_slug=schedule_slug)
- return sched.is_published()
- except:
- return False
- else:
- # Assume it is if there's no section, but the supplement has been published
- return True # sup.is_published()
- else:
- return False
- @property
- def is_best_of_2014(self):
- return self.is_in_supplement(slug='best-of-2014')
- @property
- def is_best_of_2013(self):
- return self.is_in_supplement(slug='best-of-2013')
- @property
- def is_best_of_2012(self):
- return self.is_in_supplement(slug='2012-best-of')
- @property
- def is_bea_2015(self):
- return self.is_in_supplement(slug='2015-bea-supplement')
- @property
- def is_bea_2014(self):
- return self.is_in_supplement(slug='2014-bea-supplement')
- @property
- def is_fall_preview_2014(self):
- return self.is_in_supplement(slug='2014-fall-preview')
- def get_best_of_supplement(self):
- try:
- return self.print_supplement.get(slug__startswith='best-of-')
- except:
- try:
- return self.print_supplement.get(slug__endswith='-best-of')
- except:
- return None
- def is_best_of(self):
- """Return a boolean. True if this title was ever selected as a Best-of.
- """
- if self.best_of_year is not None:
- return True
- return False
- @cached_property
- def best_of_year(self):
- """Return the year when this title was marked as a Best-of selection. None otherwise.
- """
- if self.is_best_of_2015:
- return 2015
- elif self.is_best_of_2014:
- return 2014
- elif self.is_best_of_2013:
- return 2013
- elif self.is_best_of_2012:
- return 2012
- return None
- def book_samples(self):
- return self.booksample_set.all()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement