def handle(self, *args, **options): _file = options["manifest_path"] if os.path.isfile(_file) and _file[-5:] == ".json": with open(_file) as json_file: data = json_handler.load(json_file) _type = "TUTORIAL" if data["type"].lower() == "article": _type = "ARTICLE" versioned = VersionedContent("", _type, data["title"], slugify(data["title"])) versioned.description = data["description"] if "introduction" in data: versioned.introduction = data["introduction"] if "conclusion" in data: versioned.conclusion = data["conclusion"] versioned.licence = Licence.objects.filter(code=data["licence"]).first() versioned.version = "2.0" versioned.slug = slugify(data["title"]) if "parts" in data: # if it is a big tutorial for part in data["parts"]: current_part = Container(part["title"], str(part["pk"]) + "_" + slugify(part["title"])) if "introduction" in part: current_part.introduction = part["introduction"] if "conclusion" in part: current_part.conclusion = part["conclusion"] versioned.add_container(current_part) for chapter in part["chapters"]: current_chapter = Container( chapter["title"], str(chapter["pk"]) + "_" + slugify(chapter["title"]) ) if "introduction" in chapter: current_chapter.introduction = chapter["introduction"] if "conclusion" in chapter: current_chapter.conclusion = chapter["conclusion"] current_part.add_container(current_chapter) for extract in chapter["extracts"]: current_extract = Extract( extract["title"], str(extract["pk"]) + "_" + slugify(extract["title"]) ) current_chapter.add_extract(current_extract) current_extract.text = current_extract.get_path(True) elif "chapter" in data: # if it is a mini tutorial for extract in data["chapter"]["extracts"]: current_extract = Extract(extract["title"], str(extract["pk"]) + "_" + slugify(extract["title"])) versioned.add_extract(current_extract) current_extract.text = current_extract.get_path(True) elif versioned.type == "ARTICLE": extract = Extract("text", "text") versioned.add_extract(extract) extract.text = extract.get_path(True) with open(_file, "w") as json_file: json_file.write(versioned.get_json())
def form_valid(self, form): # create the object: self.content = PublishableContent() self.content.title = form.cleaned_data["title"] self.content.description = form.cleaned_data["description"] self.content.type = form.cleaned_data["type"] self.content.licence = self.request.user.profile.licence # Use the preferred license of the user if it exists self.content.source = form.cleaned_data["source"] self.content.creation_date = datetime.now() # Creating the gallery gal = Gallery() gal.title = form.cleaned_data["title"] gal.slug = slugify(form.cleaned_data["title"]) gal.pubdate = datetime.now() gal.save() self.content.gallery = gal self.content.save() # create image: if "image" in self.request.FILES: img = Image() img.physical = self.request.FILES["image"] img.gallery = gal img.title = self.request.FILES["image"] img.slug = slugify(self.request.FILES["image"].name) img.pubdate = datetime.now() img.save() self.content.image = img self.content.save() # We need to save the content before changing its author list since it's a many-to-many relationship self.content.authors.add(self.request.user) self.content.ensure_author_gallery() self.content.save() # Add subcategories on tutorial for subcat in form.cleaned_data["subcategory"]: self.content.subcategory.add(subcat) self.content.save() # create a new repo : init_new_repo( self.content, form.cleaned_data["introduction"], form.cleaned_data["conclusion"], form.cleaned_data["msg_commit"], ) return super(CreateContent, self).form_valid(form)
def slugify_raise_on_invalid(title, use_old_slugify=False): """ use uuslug to generate a slug but if the title is incorrect (only special chars or slug is empty), an exception is raised. :param title: to be slugified title :type title: str :param use_old_slugify: use the function `slugify()` defined in zds.utils instead of the one in uuslug. Usefull \ for retro-compatibility with the old article/tutorial module, SHOULD NOT be used for the new one ! :type use_old_slugify: bool :raise InvalidSlugError: on incorrect slug :return: the slugified title :rtype: str """ if not isinstance(title, str): raise InvalidSlugError("", source=title) if not use_old_slugify: slug = slugify(title) else: slug = old_slugify(title) if not check_slug(slug): raise InvalidSlugError(slug, source=title) return slug
def create_content_gallery(form): gal = Gallery() gal.title = form.cleaned_data["title"] gal.slug = slugify(form.cleaned_data["title"]) gal.pubdate = datetime.now() gal.save() return gal
def items(self): """ :return: The last (typically 5) contents (sorted by publication date). """ subcategories = None category = self.query_params.get("category", "").strip() if category: category = get_object_or_404(Category, slug=category) subcategories = category.get_subcategories() subcategory = self.query_params.get("subcategory", "").strip() if subcategory: subcategories = [get_object_or_404(SubCategory, slug=subcategory)] tags = None tag = self.query_params.get("tag", "").strip() if tag: tags = [ get_object_or_404(Tag, slug=slugify(self.query_params.get("tag"))) ] feed_length = settings.ZDS_APP["content"]["feed_length"] contents = PublishedContent.objects.last_contents( content_type=[self.content_type], subcategories=subcategories, tags=tags)[:feed_length] return contents
def perform_create(self, title, user, subtitle=""): """Create gallery :param title: title :type title: str :param user: the user :type user: zds.member.models.User :param subtitle: subtitle :type subtitle: str :rtype: Gallery """ gallery = Gallery(title=title) gallery.subtitle = subtitle gallery.slug = slugify(title) gallery.pubdate = datetime.datetime.now() gallery.save() user_gallery = UserGallery(gallery=gallery, user=user, mode=GALLERY_WRITE) user_gallery.save() self.gallery = gallery self.users_and_permissions = {user.pk: {"read": True, "write": True}} return self.gallery
def perform_update(self, data): """Update image information :param data: things to update :type data: dict """ if "physical" in data: physical = data.get("physical") if physical.size > settings.ZDS_APP["gallery"]["image_max_size"]: raise ImageTooLarge(self.image.title, physical.size) try: ImagePIL.open(physical) except OSError: raise NotAnImage(self.image.title) self.image.physical = physical if "title" in data: self.image.title = data.get("title") self.image.slug = slugify(self.image.title) if "legend" in data: self.image.legend = data.get("legend") self.image.save() return self.image
def perform_create(self, title, physical, legend=""): """Create a new image :param title: title :type title: str :param physical: :type physical: file :param legend: legend (optional) :type legend: str """ if physical.size > settings.ZDS_APP["gallery"]["image_max_size"]: raise ImageTooLarge(title, physical.size) try: ImagePIL.open(physical) except OSError: raise NotAnImage(physical) image = Image() image.gallery = self.gallery image.title = title if legend: image.legend = legend else: image.legend = image.title image.physical = physical image.slug = slugify(title) image.pubdate = datetime.datetime.now() image.save() self.image = image return self.image
def handle(self, *args, **options): for c in PublishableContent.objects.all(): if "'" in c.title: good_slug = slugify(c.title) if c.slug != good_slug: if os.path.isdir( os.path.join( settings.ZDS_APP["content"] ["repo_private_path"], good_slug)): # this content was created before v16 and is probably broken self.stdout.write( "Fixing pre-v16 content #{} (« {} ») ... ".format( c.pk, c.title), ending="") c.save() if os.path.isdir(c.get_repo_path()): self.stdout.write("[OK]") else: self.stdout.write("[KO]") elif os.path.isdir( os.path.join( settings.ZDS_APP["content"] ["repo_private_path"], c.slug)): # this content was created during v16 and will be broken if nothing is done self.stdout.write( "Fixing in-v16 content #{} (« {} ») ... ".format( c.pk, c.title), ending="") try: versioned = c.load_version() except OSError: self.stdout.write("[KO]") else: c.sha_draft = versioned.repo_update_top_container( c.title, good_slug, versioned.get_introduction(), versioned.get_conclusion(), commit_message="[hotfix] Corrige le slug", ) c.save() if os.path.isdir(c.get_repo_path()): self.stdout.write("[OK]") else: self.stdout.write("[KO]") else: self.stderr.write( 'Content #{} (« {} ») is an orphan: there is no directory named "{}" or "{}".\n' .format(c.pk, c.title, good_slug, c.slug))
def perform_update(self, data): """Update gallery information :param data: things to update :type data: dict :rtype: Gallery """ if "title" in data: self.gallery.title = data.get("title") self.gallery.slug = slugify(self.gallery.title) if "subtitle" in data: self.gallery.subtitle = data.get("subtitle") self.gallery.save() return self.gallery
def get_queryset(self): """Filter the contents to obtain the list of contents of given type. If category parameter is provided, only contents which have this category will be listed. :return: list of contents with the right type :rtype: list of zds.tutorialv2.models.database.PublishedContent """ sub_query = "SELECT COUNT(*) FROM {} WHERE {}={} AND {}={} AND utils_comment.is_visible=1".format( "tutorialv2_contentreaction,utils_comment", "tutorialv2_contentreaction.related_content_id", "tutorialv2_publishablecontent.id", "utils_comment.id", "tutorialv2_contentreaction.comment_ptr_id", ) queryset = PublishedContent.objects.filter(must_redirect=False) # this condition got more complexe with development of zep13 # if we do filter by content_type, then every published content can be # displayed. Othewise, we have to be sure the content was expressly chosen by # someone with staff authorization. Another way to say it "it has to be a # validated content (article, tutorial), `ContentWithoutValidation` live their # own life in their own page. if self.current_content_type: queryset = queryset.filter(content_type=self.current_content_type) else: queryset = queryset.filter(~Q(content_type="OPINION")) # prefetch: queryset = ( queryset.prefetch_related("content").prefetch_related( "content__subcategory").prefetch_related("content__authors"). select_related("content__licence").select_related("content__image") .select_related("content__last_note").select_related( "content__last_note__related_content").select_related( "content__last_note__related_content__public_version" ).filter(pk=F("content__public_version__pk"))) if "category" in self.request.GET: self.subcategory = get_object_or_404( SubCategory, slug=self.request.GET.get("category")) queryset = queryset.filter( content__subcategory__in=[self.subcategory]) if "tag" in self.request.GET: self.tag = get_object_or_404( Tag, slug=slugify(self.request.GET.get("tag").lower().strip())) # TODO: fix me # different tags can have same slug such as C/C#/C++, as a first version we get all of them queryset = queryset.filter(content__tags__in=[self.tag]) queryset = queryset.extra(select={"count_note": sub_query}) return queryset.order_by("-publication_date")
def form_valid(self, form): if self.request.FILES["archive"]: try: zfile = zipfile.ZipFile(self.request.FILES["archive"], "r") except zipfile.BadZipfile: messages.error(self.request, _("Cette archive n'est pas au format ZIP.")) return self.form_invalid(form) try: new_content = UpdateContentWithArchive.extract_content_from_zip( zfile) except BadArchiveError as e: messages.error(self.request, e.message) return super(CreateContentFromArchive, self).form_invalid(form) except KeyError as e: messages.error( self.request, _(e.message + " n'est pas correctement renseigné.")) return super(CreateContentFromArchive, self).form_invalid(form) else: # Warn the user if the license has been changed manifest = json_handler.loads( str(zfile.read("manifest.json"), "utf-8")) if new_content.licence and "licence" in manifest and manifest[ "licence"] != new_content.licence.code: messages.info( self.request, _("la licence « {} » a été appliquée.".format( new_content.licence.code))) # first, create DB object (in order to get a slug) self.object = PublishableContent() self.object.title = new_content.title self.object.description = new_content.description self.object.licence = new_content.licence self.object.type = new_content.type # change of type is then allowed !! self.object.creation_date = datetime.now() self.object.save() new_content.slug = self.object.slug # new slug (choosen via DB) # Creating the gallery gal = Gallery() gal.title = new_content.title gal.slug = slugify(new_content.title) gal.pubdate = datetime.now() gal.save() # Attach user to gallery self.object.gallery = gal self.object.save() # Add subcategories on tutorial for subcat in form.cleaned_data["subcategory"]: self.object.subcategory.add(subcat) # We need to save the tutorial before changing its author list since it's a many-to-many relationship self.object.authors.add(self.request.user) self.object.save() self.object.ensure_author_gallery() # ok, now we can import introduction = "" conclusion = "" if new_content.introduction: introduction = str(zfile.read(new_content.introduction), "utf-8") if new_content.conclusion: conclusion = str(zfile.read(new_content.conclusion), "utf-8") commit_message = _("Création de « {} »").format( new_content.title) init_new_repo(self.object, introduction, conclusion, commit_message=commit_message) # copy all: versioned = self.object.load_version() try: UpdateContentWithArchive.update_from_new_version_in_zip( versioned, new_content, zfile) except BadArchiveError as e: self.object.delete() # abort content creation messages.error(self.request, e.message) return super(CreateContentFromArchive, self).form_invalid(form) # and end up by a commit !! commit_message = form.cleaned_data["msg_commit"] if not commit_message: commit_message = _( "Importation d'une archive contenant « {} »").format( new_content.title) versioned.slug = self.object.slug # force slug to ensure path resolution sha = versioned.repo_update( versioned.title, versioned.get_introduction(), versioned.get_conclusion(), commit_message, update_slug=True, ) # This HAVE TO happen after commiting files (if not, content are None) if "image_archive" in self.request.FILES: try: zfile = zipfile.ZipFile( self.request.FILES["image_archive"], "r") except zipfile.BadZipfile: messages.error( self.request, _("L'archive contenant les images n'est pas au format ZIP." )) return self.form_invalid(form) UpdateContentWithArchive.use_images_from_archive( self.request, zfile, versioned, self.object.gallery) commit_message = _( "Utilisation des images de l'archive pour « {} »" ).format(new_content.title) sha = versioned.commit_changes( commit_message) # another commit # of course, need to update sha self.object.sha_draft = sha self.object.update_date = datetime.now() self.object.save() self.success_url = reverse("content:view", args=[versioned.pk, versioned.slug]) return super(CreateContentFromArchive, self).form_valid(form)
def use_images_from_archive(request, zip_file, versioned_content, gallery): """Extract image from a gallery and then translate the ``![.+](prefix:filename)`` into the final image we want. The ``prefix`` is defined into the settings. Note that this function does not perform any commit. :param zip_file: ZIP archive :type zip_file: zipfile.ZipFile :param versioned_content: content :type versioned_content: VersionedContent :param gallery: gallery of image :type gallery: Gallery """ translation_dic = {} # create a temporary directory: temp = os.path.join(tempfile.gettempdir(), str(time.time())) if not os.path.exists(temp): os.makedirs(temp) for image_path in zip_file.namelist(): image_basename = os.path.basename(image_path) if not image_basename.strip(): # don't deal with directory continue temp_image_path = os.path.abspath( os.path.join(temp, image_basename)) # create a temporary file for the image f_im = open(temp_image_path, "wb") f_im.write(zip_file.read(image_path)) f_im.close() # if it's not an image, pass try: ImagePIL.open(temp_image_path) except OSError: continue # if size is too large, pass if os.stat(temp_image_path).st_size > settings.ZDS_APP["gallery"][ "image_max_size"]: messages.error( request, _('Votre image "{}" est beaucoup trop lourde, réduisez sa taille à moins de {:.0f}' "Kio avant de l'envoyer.").format( image_path, settings.ZDS_APP["gallery"]["image_max_size"] / 1024), ) continue # create picture in database: pic = Image() pic.gallery = gallery pic.title = image_basename pic.slug = slugify(image_basename) pic.physical = get_thumbnailer(open(temp_image_path, "rb"), relative_name=temp_image_path) pic.pubdate = datetime.now() pic.save() translation_dic[image_path] = settings.ZDS_APP["site"][ "url"] + pic.physical.url # finally, remove image if os.path.exists(temp_image_path): os.remove(temp_image_path) zip_file.close() if os.path.exists(temp): shutil.rmtree(temp) # then, modify each extracts image_regex = re.compile( r"((?P<start>!\[.*?\]\()" + settings.ZDS_APP["content"]["import_image_prefix"] + r":(?P<path>.*?)(?P<end>\)))") for element in versioned_content.traverse(only_container=False): if isinstance(element, Container): introduction = element.get_introduction() introduction = image_regex.sub( lambda g: UpdateContentWithArchive.update_image_link( g, translation_dic), introduction) conclusion = element.get_conclusion() conclusion = image_regex.sub( lambda g: UpdateContentWithArchive.update_image_link( g, translation_dic), conclusion) element.repo_update(element.title, introduction, conclusion, do_commit=False) else: section_text = element.get_text() section_text = image_regex.sub( lambda g: UpdateContentWithArchive.update_image_link( g, translation_dic), section_text) element.repo_update(element.title, section_text, do_commit=False)
def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) if self.kwargs.get("slug", False): self.level = 2 self.max_last_contents = settings.ZDS_APP["content"][ "max_last_publications_level_2"] if self.kwargs.get("slug_category", False): self.level = 3 self.max_last_contents = settings.ZDS_APP["content"][ "max_last_publications_level_3"] if (self.request.GET.get("category", False) or self.request.GET.get("subcategory", False) or self.request.GET.get("type", False) or self.request.GET.get("tag", False)): self.level = 4 self.max_last_contents = 50 self.template_name = self.templates[self.level] recent_kwargs = {} if self.level == 1: # get categories and subcategories categories = ViewPublications.categories_with_contents_count( self.handle_types) context["categories"] = categories context["content_count"] = PublishedContent.objects.last_contents( content_type=self.handle_types, with_comments_count=False).count() elif self.level == 2: context["category"] = get_object_or_404( Category, slug=self.kwargs.get("slug")) context[ "subcategories"] = ViewPublications.subcategories_with_contents_count( context["category"], self.handle_types) recent_kwargs["subcategories"] = context["subcategories"] elif self.level == 3: subcategory = get_object_or_404(SubCategory, slug=self.kwargs.get("slug")) context["category"] = subcategory.get_parent_category() if context["category"].slug != self.kwargs.get("slug_category"): raise Http404("wrong slug for category ({} != {})".format( context["category"].slug, self.kwargs.get("slug_category"))) context["subcategory"] = subcategory recent_kwargs["subcategories"] = [subcategory] elif self.level == 4: category = self.request.GET.get("category", None) subcategory = self.request.GET.get("subcategory", None) subcategories = None if category is not None: context["category"] = get_object_or_404(Category, slug=category) subcategories = context["category"].get_subcategories() elif subcategory is not None: subcategory = get_object_or_404( SubCategory, slug=self.request.GET.get("subcategory")) context["category"] = subcategory.get_parent_category() context["subcategory"] = subcategory subcategories = [subcategory] content_type = self.handle_types context["type"] = None if "type" in self.request.GET: _type = self.request.GET.get("type", "").upper() if _type in self.handle_types: content_type = _type context["type"] = TYPE_CHOICES_DICT[_type] else: raise Http404(f"wrong type {_type}") tag = self.request.GET.get("tag", None) tags = None if tag is not None: tags = [get_object_or_404(Tag, slug=slugify(tag))] context["tag"] = tags[0] contents_queryset = PublishedContent.objects.last_contents( subcategories=subcategories, tags=tags, content_type=content_type) items_per_page = settings.ZDS_APP["content"]["content_per_page"] make_pagination( context, self.request, contents_queryset, items_per_page, context_list_name="filtered_contents", with_previous_item=False, ) if self.level < 4: last_articles = PublishedContent.objects.last_contents( **dict(content_type="ARTICLE", **recent_kwargs)) context["last_articles"] = last_articles[:self.max_last_contents] context["more_articles"] = last_articles.count( ) > self.max_last_contents last_tutorials = PublishedContent.objects.last_contents( **dict(content_type="TUTORIAL", **recent_kwargs)) context["last_tutorials"] = last_tutorials[:self.max_last_contents] context["more_tutorials"] = last_tutorials.count( ) > self.max_last_contents context["beta_forum"] = ( Forum.objects.prefetch_related("category").filter( pk=settings.ZDS_APP["forum"]["beta_forum_id"]).last()) context["level"] = self.level return context
def form_valid(self, form): versioned = self.versioned_object publishable = self.object # check if content has changed: current_hash = versioned.compute_hash() if current_hash != form.cleaned_data["last_hash"]: data = form.data.copy() data["last_hash"] = current_hash data["introduction"] = versioned.get_introduction() data["conclusion"] = versioned.get_conclusion() form.data = data messages.error( self.request, _("Une nouvelle version a été postée avant que vous ne validiez." )) return self.form_invalid(form) # Forbid removing all categories of a validated content if publishable.in_public() and not form.cleaned_data["subcategory"]: messages.error( self.request, _("Vous devez choisir au moins une catégorie, car ce contenu est déjà publié." )) return self.form_invalid(form) # first, update DB (in order to get a new slug if needed) title_is_changed = publishable.title != form.cleaned_data["title"] publishable.title = form.cleaned_data["title"] publishable.description = form.cleaned_data["description"] publishable.source = form.cleaned_data["source"] publishable.update_date = datetime.now() # update gallery and image: gal = Gallery.objects.filter(pk=publishable.gallery.pk) gal.update(title=publishable.title) gal.update(slug=slugify(publishable.title)) gal.update(update=datetime.now()) if "image" in self.request.FILES: img = Image() img.physical = self.request.FILES["image"] img.gallery = publishable.gallery img.title = self.request.FILES["image"] img.slug = slugify(self.request.FILES["image"].name) img.pubdate = datetime.now() img.save() publishable.image = img publishable.save(force_slug_update=title_is_changed) logger.debug("content %s updated, slug is %s", publishable.pk, publishable.slug) # now, update the versioned information versioned.description = form.cleaned_data["description"] sha = versioned.repo_update_top_container( form.cleaned_data["title"], publishable.slug, form.cleaned_data["introduction"], form.cleaned_data["conclusion"], form.cleaned_data["msg_commit"], ) logger.debug("slug consistency after repo update repo=%s db=%s", versioned.slug, publishable.slug) # update relationships : publishable.sha_draft = sha publishable.subcategory.clear() for subcat in form.cleaned_data["subcategory"]: publishable.subcategory.add(subcat) publishable.save() self.success_url = reverse("content:view", args=[publishable.pk, publishable.slug]) return super().form_valid(form)