class PublishedContent(models.Model): """A class that contains information on the published version of a content. Used for quick url resolution, quick listing, and to know where the public version of the files are. Linked to a ``PublishableContent`` for the rest. Don't forget to add a ``.prefetch_related("content")`` !! """ # TODO: by playing with this class, it may solve most of the SEO problems !! class Meta: verbose_name = 'Contenu publié' verbose_name_plural = 'Contenus publiés' content = models.ForeignKey(PublishableContent, verbose_name='Contenu') content_type = models.CharField(max_length=10, choices=TYPE_CHOICES, db_index=True, verbose_name='Type de contenu') content_public_slug = models.CharField('Slug du contenu publié', max_length=80) content_pk = models.IntegerField('Pk du contenu publié', db_index=True) publication_date = models.DateTimeField('Date de publication', db_index=True, blank=True, null=True) sha_public = models.CharField('Sha1 de la version publiée', blank=True, null=True, max_length=80, db_index=True) must_redirect = models.BooleanField( 'Redirection vers une version plus récente', blank=True, db_index=True, default=False) objects = PublishedContentManager() def __unicode__(self): return _('Version publique de "{}"').format(self.content.title) def get_prod_path(self, relative=False): if not relative: return os.path.join(settings.ZDS_APP['content']['repo_public_path'], self.content_public_slug) else: return '' def get_absolute_url_online(self): """ :return: the URL of the published content :rtype: str """ reversed_ = '' if self.is_article(): reversed_ = 'article' elif self.is_tutorial(): reversed_ = 'tutorial' return reverse(reversed_ + ':view', kwargs={'pk': self.content_pk, 'slug': self.content_public_slug}) def load_public_version_or_404(self): """ :return: the public content :rtype: zds.tutorialv2.models.models_database.PublicContent :raise Http404: if the version is not available """ return self.content.load_version_or_404(sha=self.sha_public, public=self) def load_public_version(self): """ :rtype: zds.tutorialv2.models.models_database.PublicContent :return: the public content """ return self.content.load_version(sha=self.sha_public, public=self) def is_article(self): """ :return: ``True`` if it is an article, ``False`` otherwise. :rtype: bool """ return self.content_type == "ARTICLE" def is_tutorial(self): """ :return: ``True`` if it is an article, ``False`` otherwise. :rtype: bool """ return self.content_type == "TUTORIAL" def get_extra_contents_directory(self): """ :return: path to all the "extra contents" :rtype: str """ return os.path.join(self.get_prod_path(), settings.ZDS_APP['content']['extra_contents_dirname']) def have_type(self, type_): """check if a given extra content exists :return: ``True`` if the file exists, ``False`` otherwhise :rtype: bool """ allowed_types = ['pdf', 'md', 'html', 'epub', 'zip'] if type_ in allowed_types: return os.path.isfile( os.path.join(self.get_extra_contents_directory(), self.content_public_slug + '.' + type_)) return False def have_md(self): """Check if the markdown version of the content is available :return: ``True`` if available, ``False`` otherwise :rtype: bool """ return self.have_type('md') def have_html(self): """Check if the html version of the content is available :return: ``True`` if available, ``False`` otherwise :rtype: bool """ return self.have_type('html') def have_pdf(self): """Check if the pdf version of the content is available :return: ``True`` if available, ``False`` otherwise :rtype: bool """ return self.have_type('pdf') def have_epub(self): """Check if the standard epub version of the content is available :return: ``True`` if available, ``False`` otherwise :rtype: bool """ return self.have_type('epub') def have_zip(self): """Check if the standard epub version of the content is available :return: ``True`` if available, ``False`` otherwise :rtype: bool """ return self.have_type('zip') def get_absolute_url_to_extra_content(self, type_): """Get the url that point to the extra content the user may want to download :param type_: the type inside allowed_type :type type_: str :return: URL to a given extra content (note that no check for existence is done) :rtype: str """ allowed_types = ['pdf', 'md', 'html', 'epub', 'zip'] if type_ in allowed_types: reversed_ = '' if self.is_article(): reversed_ = 'article' elif self.is_tutorial(): reversed_ = 'tutorial' return reverse( reversed_ + ':download-' + type_, kwargs={'pk': self.content_pk, 'slug': self.content_public_slug}) return '' def get_absolute_url_md(self): """wrapper around ``self.get_absolute_url_to_extra_content('md')`` :return: URL to the full markdown version of the published content :rtype: str """ return self.get_absolute_url_to_extra_content('md') def get_absolute_url_html(self): """wrapper around ``self.get_absolute_url_to_extra_content('html')`` :return: URL to the HTML version of the published content :rtype: str """ return self.get_absolute_url_to_extra_content('html') def get_absolute_url_pdf(self): """wrapper around ``self.get_absolute_url_to_extra_content('pdf')`` :return: URL to the PDF version of the published content :rtype: str """ return self.get_absolute_url_to_extra_content('pdf') def get_absolute_url_epub(self): """wrapper around ``self.get_absolute_url_to_extra_content('epub')`` :return: URL to the epub version of the published content :rtype: str """ return self.get_absolute_url_to_extra_content('epub') def get_absolute_url_zip(self): """wrapper around ``self.get_absolute_url_to_extra_content('zip')`` :return: URL to the zip archive of the published content :rtype: str """ return self.get_absolute_url_to_extra_content('zip')
class PublishedContent(models.Model): """A class that contains information on the published version of a content. Used for quick url resolution, quick listing, and to know where the public version of the files are. Linked to a ``PublishableContent`` for the rest. Don't forget to add a ``.prefetch_related('content')`` !! """ class Meta: verbose_name = 'Contenu publié' verbose_name_plural = 'Contenus publiés' content = models.ForeignKey(PublishableContent, verbose_name='Contenu') content_type = models.CharField(max_length=10, choices=TYPE_CHOICES, db_index=True, verbose_name='Type de contenu') content_public_slug = models.CharField('Slug du contenu publié', max_length=80) content_pk = models.IntegerField('Pk du contenu publié', db_index=True) publication_date = models.DateTimeField('Date de publication', db_index=True, blank=True, null=True) update_date = models.DateTimeField('Date de mise à jour', db_index=True, blank=True, null=True, default=None) sha_public = models.CharField('Sha1 de la version publiée', blank=True, null=True, max_length=80, db_index=True) nb_letter = models.IntegerField(default=None, null=True, verbose_name=b'Nombre de lettres du contenu', blank=True) must_redirect = models.BooleanField( 'Redirection vers une version plus récente', blank=True, db_index=True, default=False) authors = models.ManyToManyField(User, verbose_name='Auteurs', db_index=True) objects = PublishedContentManager() versioned_model = None # sizes contain a python dict (as a string in database) with all information about file sizes sizes = models.CharField('Tailles des fichiers téléchargeables', max_length=512, default='{}') def __str__(self): return _('Version publique de "{}"').format(self.content.title) def title(self): if self.versioned_model: return self.versioned_model.title else: return self.load_public_version().title def description(self): if self.versioned_model: return self.versioned_model.description else: return self.load_public_version().description def get_prod_path(self, relative=False): if not relative: return os.path.join(settings.ZDS_APP['content']['repo_public_path'], self.content_public_slug) else: return '' def get_absolute_url_online(self): """ :return: the URL of the published content :rtype: str """ reversed_ = self.content_type.lower() return reverse(reversed_ + ':view', kwargs={'pk': self.content_pk, 'slug': self.content_public_slug}) def load_public_version_or_404(self): """ :return: the public content :rtype: zds.tutorialv2.models.models_database.PublicContent :raise Http404: if the version is not available """ self.versioned_model = self.content.load_version_or_404(sha=self.sha_public, public=self) return self.versioned_model def load_public_version(self): """ :rtype: zds.tutorialv2.models.models_database.PublicContent :return: the public content """ self.versioned_model = self.content.load_version(sha=self.sha_public, public=self) return self.versioned_model def is_article(self): """ :return: ``True`` if it is an article, ``False`` otherwise. :rtype: bool """ return self.content_type == 'ARTICLE' def is_tutorial(self): """ :return: ``True`` if it is an article, ``False`` otherwise. :rtype: bool """ return self.content_type == 'TUTORIAL' def get_extra_contents_directory(self): """ :return: path to all the 'extra contents' :rtype: str """ return os.path.join(self.get_prod_path(), settings.ZDS_APP['content']['extra_contents_dirname']) def have_type(self, type_): """check if a given extra content exists :return: ``True`` if the file exists, ``False`` otherwhise :rtype: bool """ if type_ in ALLOWED_TYPES: return os.path.isfile( os.path.join(self.get_extra_contents_directory(), self.content_public_slug + '.' + type_)) return False def have_md(self): """Check if the markdown version of the content is available :return: ``True`` if available, ``False`` otherwise :rtype: bool """ return self.have_type('md') def have_html(self): """Check if the html version of the content is available :return: ``True`` if available, ``False`` otherwise :rtype: bool """ return self.have_type('html') def have_pdf(self): """Check if the pdf version of the content is available :return: ``True`` if available, ``False`` otherwise :rtype: bool """ return self.have_type('pdf') def have_epub(self): """Check if the standard epub version of the content is available :return: ``True`` if available, ``False`` otherwise :rtype: bool """ return self.have_type('epub') def have_zip(self): """Check if the standard zip version of the content is available :return: ``True`` if available, ``False`` otherwise :rtype: bool """ return self.have_type('zip') def get_size_file_type(self, type_): """ Get the size of a given extra content. Is the size is not in database we get it and store it for next time. :return: size of file :rtype: int """ if type_ in ALLOWED_TYPES: sizes = eval(str(self.sizes)) try: size = sizes[type_] except KeyError: # if size is not in database we store it sizes[type_] = os.path.getsize(os.path.join( self.get_extra_contents_directory(), self.content_public_slug + '.' + type_)) self.sizes = sizes self.save() size = sizes[type_] return size return None def get_size_md(self): """Get the size of md :return: size of file :rtype: int """ return self.get_size_file_type('md') def get_size_html(self): """Get the size of html :return: size of file :rtype: int """ return self.get_size_file_type('html') def get_size_pdf(self): """Get the size of pdf :return: size of file :rtype: int """ return self.get_size_file_type('pdf') def get_size_epub(self): """Get the size of epub :return: size of file :rtype: int """ return self.get_size_file_type('epub') def get_size_zip(self): """Get the size of zip :return: size of file :rtype: int """ return self.get_size_file_type('zip') def get_absolute_url_to_extra_content(self, type_): """Get the url that point to the extra content the user may want to download :param type_: the type inside allowed_type :type type_: str :return: URL to a given extra content (note that no check for existence is done) :rtype: str """ if type_ in ALLOWED_TYPES: reversed_ = self.content_type.lower() return reverse( reversed_ + ':download-' + type_, kwargs={'pk': self.content_pk, 'slug': self.content_public_slug}) return '' def get_absolute_url_md(self): """wrapper around ``self.get_absolute_url_to_extra_content('md')`` :return: URL to the full markdown version of the published content :rtype: str """ return self.get_absolute_url_to_extra_content('md') def get_absolute_url_html(self): """wrapper around ``self.get_absolute_url_to_extra_content('html')`` :return: URL to the HTML version of the published content :rtype: str """ return self.get_absolute_url_to_extra_content('html') def get_absolute_url_pdf(self): """wrapper around ``self.get_absolute_url_to_extra_content('pdf')`` :return: URL to the PDF version of the published content :rtype: str """ return self.get_absolute_url_to_extra_content('pdf') def get_absolute_url_epub(self): """wrapper around ``self.get_absolute_url_to_extra_content('epub')`` :return: URL to the epub version of the published content :rtype: str """ return self.get_absolute_url_to_extra_content('epub') def get_absolute_url_zip(self): """wrapper around ``self.get_absolute_url_to_extra_content('zip')`` :return: URL to the zip archive of the published content :rtype: str """ return self.get_absolute_url_to_extra_content('zip') def get_last_action_date(self): return self.update_date or self.publication_date def get_nb_letters(self, md_file_path=None): """ Compute the number of letters for a given content :param md_file_path: use another file to compute the number of letter rather than the default one. :type md_file_path: str :return: Number of letters in the md file :rtype: int """ if not md_file_path: md_file_path = os.path.join(self.get_extra_contents_directory(), self.content_public_slug + '.md') try: with open(md_file_path, 'rb') as md_file: content = md_file.read().decode('utf-8') current_content = PublishedContent.objects.filter(content_pk=self.content_pk, must_redirect=False).first() if current_content: return len(content) except IOError as e: logger.warning('could not get file %s to compute nb letters (error=%s)', md_file_path, e)