wbwriter 1.54.17__tar.gz → 1.59.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of wbwriter might be problematic. Click here for more details.

Files changed (83) hide show
  1. {wbwriter-1.54.17 → wbwriter-1.59.4}/PKG-INFO +1 -1
  2. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/admin.py +1 -3
  3. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/factories/article.py +1 -0
  4. wbwriter-1.59.4/wbwriter/migrations/0013_articletype_allow_empty_author_and_more.py +23 -0
  5. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/models/article.py +36 -74
  6. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/models/article_type.py +3 -1
  7. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/models/in_editor_template.py +1 -1
  8. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/models/meta_information.py +2 -2
  9. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/models/publication_models.py +4 -1
  10. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/pdf_generator.py +2 -2
  11. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/publication_parser.py +3 -4
  12. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/serializers/article.py +3 -7
  13. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/tests/signals.py +1 -0
  14. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/tests/test_filter.py +1 -0
  15. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/tests/test_model.py +2 -1
  16. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/tests/test_writer.py +1 -0
  17. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/urls.py +0 -1
  18. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/viewsets/buttons.py +1 -1
  19. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/viewsets/menu.py +0 -9
  20. {wbwriter-1.54.17 → wbwriter-1.59.4}/.gitignore +0 -0
  21. {wbwriter-1.54.17 → wbwriter-1.59.4}/pyproject.toml +0 -0
  22. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/__init__.py +0 -0
  23. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/apps.py +0 -0
  24. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/dynamic_preferences_registry.py +0 -0
  25. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/factories/__init__.py +0 -0
  26. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/factories/meta_information.py +0 -0
  27. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/filters/__init__.py +0 -0
  28. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/filters/article.py +0 -0
  29. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/filters/metainformationinstance.py +0 -0
  30. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/locale/de/LC_MESSAGES/django.mo +0 -0
  31. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/locale/de/LC_MESSAGES/django.po +0 -0
  32. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/locale/en/LC_MESSAGES/django.mo +0 -0
  33. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/locale/en/LC_MESSAGES/django.po +0 -0
  34. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/locale/fr/LC_MESSAGES/django.mo +0 -0
  35. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/locale/fr/LC_MESSAGES/django.po +0 -0
  36. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/migrations/0001_initial_squashed_squashed_0008_alter_article_author_alter_article_feedback_contact_and_more.py +0 -0
  37. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/migrations/0009_dependantarticle.py +0 -0
  38. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/migrations/0010_alter_article_options.py +0 -0
  39. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/migrations/0011_auto_20240103_0953.py +0 -0
  40. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/migrations/0012_i18n.py +0 -0
  41. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/migrations/__init__.py +0 -0
  42. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/models/__init__.py +0 -0
  43. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/models/block.py +0 -0
  44. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/models/block_parameter.py +0 -0
  45. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/models/mixins.py +0 -0
  46. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/models/style.py +0 -0
  47. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/models/template.py +0 -0
  48. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/serializers/__init__.py +0 -0
  49. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/serializers/article_type.py +0 -0
  50. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/serializers/in_editor_template.py +0 -0
  51. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/serializers/meta_information.py +0 -0
  52. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/serializers/publication.py +0 -0
  53. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/templates/wbwriter/article_pdf.html +0 -0
  54. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/templates/wbwriter/templatetags/cite_article.html +0 -0
  55. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/templates/wbwriter/templatetags/table_of_contents.html +0 -0
  56. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/templates/wbwriter/templatetags/table_of_contents_articles.html +0 -0
  57. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/templatetags/__init__.py +0 -0
  58. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/templatetags/writer.py +0 -0
  59. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/tests/__init__.py +0 -0
  60. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/tests/conftest.py +0 -0
  61. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/tests/tests.py +0 -0
  62. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/typings.py +0 -0
  63. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/viewsets/__init__.py +0 -0
  64. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/viewsets/article.py +0 -0
  65. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/viewsets/article_type.py +0 -0
  66. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/viewsets/display/__init__.py +0 -0
  67. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/viewsets/display/article.py +0 -0
  68. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/viewsets/display/article_type.py +0 -0
  69. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/viewsets/display/in_editor_template.py +0 -0
  70. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/viewsets/display/meta_information.py +0 -0
  71. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/viewsets/display/meta_information_instance.py +0 -0
  72. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/viewsets/display/publication.py +0 -0
  73. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/viewsets/documentation/article.md +0 -0
  74. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/viewsets/endpoints/__init__.py +0 -0
  75. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/viewsets/endpoints/article.py +0 -0
  76. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/viewsets/endpoints/meta_information.py +0 -0
  77. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/viewsets/in_editor_template.py +0 -0
  78. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/viewsets/meta_information.py +0 -0
  79. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/viewsets/meta_information_instance.py +0 -0
  80. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/viewsets/publication.py +0 -0
  81. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/viewsets/titles/__init__.py +0 -0
  82. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/viewsets/titles/publication_title_config.py +0 -0
  83. {wbwriter-1.54.17 → wbwriter-1.59.4}/wbwriter/viewsets/titles/reviewer_article_title_config.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wbwriter
3
- Version: 1.54.17
3
+ Version: 1.59.4
4
4
  Author-email: Christopher Wittlinger <c.wittlinger@stainly.com>
5
5
  Requires-Dist: reportlab==3.*
6
6
  Requires-Dist: wbcore
@@ -51,7 +51,7 @@ class MetaInformationInstanceInline(admin.TabularInline):
51
51
 
52
52
  @admin.register(ArticleType)
53
53
  class ArticleTypeModelAdmin(admin.ModelAdmin):
54
- list_display = ("id", "label", "slug")
54
+ list_display = ("id", "label", "slug", "can_be_published", "allow_empty_author")
55
55
 
56
56
  ordering = ["slug"]
57
57
  search_fields = ["id", "label", "slug"]
@@ -61,8 +61,6 @@ class ArticleTypeModelAdmin(admin.ModelAdmin):
61
61
  "qa_reviewers",
62
62
  ]
63
63
 
64
- # prepopulated_fields = {"slug": ("label",)}
65
-
66
64
 
67
65
  @admin.register(Article)
68
66
  class ArticleModelAdmin(CompareVersionAdmin, admin.ModelAdmin):
@@ -80,6 +80,7 @@ class ArticleTypeFactory(factory.django.DjangoModelFactory):
80
80
  model = ArticleType
81
81
 
82
82
  label = factory.Sequence(lambda n: f"articletype {n}")
83
+ can_be_published = True
83
84
 
84
85
  @factory.post_generation
85
86
  def parsers(self, create, extracted, **kwargs):
@@ -0,0 +1,23 @@
1
+ # Generated by Django 5.0.12 on 2025-12-02 10:19
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('wbwriter', '0012_i18n'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AddField(
14
+ model_name='articletype',
15
+ name='allow_empty_author',
16
+ field=models.BooleanField(default=False),
17
+ ),
18
+ migrations.AddField(
19
+ model_name='articletype',
20
+ name='can_be_published',
21
+ field=models.BooleanField(default=True),
22
+ ),
23
+ ]
@@ -39,7 +39,7 @@ from weasyprint import CSS
39
39
 
40
40
  from wbwriter.models.publication_models import Publication
41
41
  from wbwriter.pdf_generator import PdfGenerator
42
- from wbwriter.publication_parser import ParserValidationException
42
+ from wbwriter.publication_parser import ParserValidationError
43
43
  from wbwriter.typings import ArticleDTO
44
44
 
45
45
  from .mixins import PublishableMixin
@@ -167,7 +167,7 @@ class DependantArticle(WBModel):
167
167
  )
168
168
 
169
169
  @classmethod
170
- def get_endpoint_basename(self):
170
+ def get_endpoint_basename(cls):
171
171
  return "wbwriter:dependantarticle"
172
172
 
173
173
  @classmethod
@@ -770,16 +770,10 @@ class Article(CloneMixin, TagModelMixin, PublishableMixin, WBModel):
770
770
  generate_publications.delay(self.id)
771
771
 
772
772
  def can_publish(self) -> dict[str, str]:
773
- """Allow only one-off and deep-dive articles to be published."""
774
773
  errors = dict()
775
774
  if self.status not in [self.Status.APPROVED, self.Status.PUBLISHED]:
776
775
  errors["status"] = [gettext_lazy("Status needs to be approved in order to allow publication")]
777
- if not self.type or self.type.slug not in [
778
- "one-off-article",
779
- "deep-dive-article",
780
- "mid-year-review",
781
- "year-s-favorites",
782
- ]:
776
+ if not self.type or not self.type.can_be_published:
783
777
  errors["type"] = [gettext_lazy("unvalid type for publication")]
784
778
  # We ensure the article's parser can be published
785
779
  if article_type := self.type:
@@ -789,7 +783,7 @@ class Article(CloneMixin, TagModelMixin, PublishableMixin, WBModel):
789
783
  self._build_dto(),
790
784
  date.today(),
791
785
  ).is_valid()
792
- except ParserValidationException as e:
786
+ except ParserValidationError as e:
793
787
  errors["non_field_errors"] = e.errors
794
788
  except ModuleNotFoundError:
795
789
  errors["non_field_errors"] = [gettext_lazy("invalid parser")]
@@ -811,20 +805,6 @@ class Article(CloneMixin, TagModelMixin, PublishableMixin, WBModel):
811
805
  # we delete existing publication in case the user unpublish it
812
806
  Publication.objects.filter(object_id=self.id, content_type=ContentType.objects.get_for_model(self)).delete()
813
807
 
814
- # @transition(
815
- # status,
816
- # source=[Status.APPROVED, Status.PUBLISHED],
817
- # target=Status.DRAFT,
818
- # permission=can_revise,
819
- # custom={"_transition_button": revise_button},
820
- # )
821
- # def revise(self, by=None):
822
- # """Move the article back to the draft state."""
823
- # self.peer_reviewer_approved = False
824
- # self.peer_reviewer = None
825
- # if self.type.slug == "ddq":
826
- # self.author = None
827
-
828
808
  @transition(
829
809
  status,
830
810
  source=[Status.APPROVED],
@@ -867,10 +847,6 @@ class Article(CloneMixin, TagModelMixin, PublishableMixin, WBModel):
867
847
  if not self.qa_reviewer:
868
848
  self.qa_reviewer = self.roll_qa()
869
849
 
870
- # if self.type and self.type.slug == "ddq":
871
- # with suppress(Person.DoesNotExist):
872
- # self.qa_reviewer = Person.objects.get(computed_str__icontains="Stefano Rodella")
873
-
874
850
  create_dependencies(self)
875
851
  super().save(*args, **kwargs)
876
852
 
@@ -1048,58 +1024,44 @@ class Article(CloneMixin, TagModelMixin, PublishableMixin, WBModel):
1048
1024
  styles = []
1049
1025
  pdf_content = None
1050
1026
  if not self.content or "sections" not in self.content:
1051
- html = template.Template(empty_template).render(
1052
- {"content": "<h1>There seems to be no content to render.</h1>"}
1053
- )
1027
+ template_context = {"content": "<h1>There seems to be no content to render.</h1>"}
1054
1028
  else:
1055
1029
  content = self.content["sections"][self.content["sectionOrder"][0]]["content"]["content"]
1056
1030
  resolved_content = resolve_content(content, extensions=["sane_lists"], article=self)
1057
1031
  template_context = template.Context({"content": resolved_content})
1058
1032
  if self.type:
1059
- if "ddq" in self.type.slug:
1060
- # TODO: Replace self.content with the raw_contents equivalent.
1061
-
1062
- # If there is a template defined, then render that one and use its styles.
1063
- if self.template and self.template.template:
1064
- html = template.Template(self.template.template).render(template_context)
1065
-
1066
- styles = [CSS(string=style.style) for style in self.template.styles.all()]
1067
- if header := self.template.header_template:
1068
- styles += [CSS(string=style.style) for style in header.styles.all()]
1069
- if footer := self.template.footer_template:
1070
- styles += [CSS(string=style.style) for style in footer.styles.all()]
1071
-
1072
- pdf_content = PdfGenerator(
1073
- main_html=html,
1074
- header_html=self.template.header_template.template,
1075
- footer_html=self.template.footer_template.template,
1076
- custom_css=styles,
1077
- side_margin=self.template.side_margin,
1078
- extra_vertical_margin=self.template.extra_vertical_margin,
1079
- ).render_pdf()
1080
-
1081
- elif (
1082
- "one-off" in self.type.slug
1083
- or "deep-dive" in self.type.slug
1084
- or "mid-year" in self.type.slug
1085
- or "year-s-favorites" in self.type.slug
1086
- ):
1087
- parsers = self.type.parsers.all()
1088
- if parsers.exists():
1089
- parser = parsers.first().parser_class(self._build_dto(), datetime.date.today())
1090
- if parser.is_valid(raise_exception=False):
1091
- pdf_content = parser.get_file()
1092
- else:
1093
- template_context["content"] = f"""
1094
- <p>To generate the PDF, please correct the following errors:</p>
1095
- <ul>
1096
- {"".join([f"<li>{error}</li>" for error in parser.errors])}
1097
- </ul>
1098
- """
1099
- html = template.Template(empty_template).render(template_context)
1033
+ parsers = self.type.parsers.all()
1034
+ if parsers.exists():
1035
+ parser = parsers.first().parser_class(self._build_dto(), datetime.date.today())
1036
+ if parser.is_valid(raise_exception=False):
1037
+ pdf_content = parser.get_file()
1038
+ else:
1039
+ template_context["content"] = f"""
1040
+ <p>To generate the PDF, please correct the following errors:</p>
1041
+ <ul>
1042
+ {"".join([f"<li>{error}</li>" for error in parser.errors])}
1043
+ </ul>
1044
+ """
1045
+ elif self.template and self.template.template:
1046
+ html = template.Template(self.template.template).render(template_context)
1047
+
1048
+ styles = [CSS(string=style.style) for style in self.template.styles.all()]
1049
+ if header := self.template.header_template:
1050
+ styles += [CSS(string=style.style) for style in header.styles.all()]
1051
+ if footer := self.template.footer_template:
1052
+ styles += [CSS(string=style.style) for style in footer.styles.all()]
1053
+
1054
+ pdf_content = PdfGenerator(
1055
+ main_html=html,
1056
+ header_html=self.template.header_template.template,
1057
+ footer_html=self.template.footer_template.template,
1058
+ custom_css=styles,
1059
+ side_margin=self.template.side_margin,
1060
+ extra_vertical_margin=self.template.extra_vertical_margin,
1061
+ ).render_pdf()
1100
1062
  if not pdf_content:
1101
1063
  pdf_content = PdfGenerator(
1102
- main_html=html,
1064
+ main_html=template.Template(empty_template).render(template_context),
1103
1065
  custom_css=styles,
1104
1066
  side_margin=0,
1105
1067
  extra_vertical_margin=0,
@@ -1175,7 +1137,7 @@ class Article(CloneMixin, TagModelMixin, PublishableMixin, WBModel):
1175
1137
  return mapping
1176
1138
 
1177
1139
  @classmethod
1178
- def get_endpoint_basename(self):
1140
+ def get_endpoint_basename(cls):
1179
1141
  return "wbwriter:article"
1180
1142
 
1181
1143
  @classmethod
@@ -30,9 +30,11 @@ class ArticleType(WBModel):
30
30
  parsers = models.ManyToManyField("wbwriter.PublicationParser", related_name="publication_parsers", blank=True)
31
31
  peer_reviewers = models.ManyToManyField("directory.Person", related_name="article_type_peer_reviewers")
32
32
  qa_reviewers = models.ManyToManyField("directory.Person", related_name="article_type_qa_reviewers")
33
+ can_be_published = models.BooleanField(default=True)
34
+ allow_empty_author = models.BooleanField(default=False)
33
35
 
34
36
  @classmethod
35
- def get_endpoint_basename(self):
37
+ def get_endpoint_basename(cls):
36
38
  return "wbwriter:articletyperepresentation"
37
39
 
38
40
  @classmethod
@@ -86,7 +86,7 @@ class InEditorTemplate(WBModel):
86
86
  return self.title
87
87
 
88
88
  @classmethod
89
- def get_endpoint_basename(self):
89
+ def get_endpoint_basename(cls):
90
90
  return "wbwriter:in-editor-template"
91
91
 
92
92
  @classmethod
@@ -24,7 +24,7 @@ class MetaInformation(WBModel):
24
24
  verbose_name_plural = "Meta Information"
25
25
 
26
26
  @classmethod
27
- def get_endpoint_basename(self):
27
+ def get_endpoint_basename(cls):
28
28
  return "wbwriter:metainformation"
29
29
 
30
30
  @classmethod
@@ -60,7 +60,7 @@ class MetaInformationInstance(WBModel):
60
60
  ]
61
61
 
62
62
  @classmethod
63
- def get_endpoint_basename(self):
63
+ def get_endpoint_basename(cls):
64
64
  return "wbwriter:metainformationinstance"
65
65
 
66
66
  @classmethod
@@ -77,6 +77,9 @@ class Publication(TagModelMixin, models.Model):
77
77
  object_id = models.PositiveIntegerField()
78
78
  content_object = GenericForeignKey("content_type", "object_id")
79
79
 
80
+ def __str__(self) -> str:
81
+ return self.title
82
+
80
83
  @classmethod
81
84
  def create_or_update_from_parser_and_object(cls, parser, generic_object):
82
85
  if hasattr(generic_object, "_build_dto") and callable(generic_object._build_dto):
@@ -91,7 +94,7 @@ class Publication(TagModelMixin, models.Model):
91
94
  pub.save()
92
95
 
93
96
  @classmethod
94
- def get_endpoint_basename(self):
97
+ def get_endpoint_basename(cls):
95
98
  return "wbwriter:publication"
96
99
 
97
100
  @classmethod
@@ -31,7 +31,7 @@ class PdfGenerator:
31
31
  main_html,
32
32
  header_html=None,
33
33
  footer_html=None,
34
- custom_css=[],
34
+ custom_css=None,
35
35
  base_url=None,
36
36
  side_margin=2,
37
37
  extra_vertical_margin=30,
@@ -61,7 +61,7 @@ class PdfGenerator:
61
61
  self.main_html = main_html
62
62
  self.header_html = header_html
63
63
  self.footer_html = footer_html
64
- self.custom_css = custom_css
64
+ self.custom_css = custom_css if custom_css else []
65
65
  self.base_url = base_url
66
66
  self.side_margin = side_margin
67
67
  self.extra_vertical_margin = extra_vertical_margin
@@ -1,6 +1,5 @@
1
1
  import json
2
2
  import os
3
- from abc import ABC
4
3
  from datetime import date
5
4
  from io import BytesIO
6
5
  from typing import Iterable
@@ -17,12 +16,12 @@ from wbwriter.pdf_generator import PdfGenerator
17
16
  from .typings import ArticleDTO
18
17
 
19
18
 
20
- class ParserValidationException(Exception):
19
+ class ParserValidationError(Exception):
21
20
  def __init__(self, *args, errors=None, **kwargs): # real signature unknown
22
21
  self.errors = errors
23
22
 
24
23
 
25
- class PublicationParser(ABC): # pragma: no cover
24
+ class PublicationParser: # pragma: no cover
26
25
  def __init__(self, article: ArticleDTO, published_date: date):
27
26
  self.article = article
28
27
  self.published_date = published_date
@@ -64,7 +63,7 @@ class PublicationParser(ABC): # pragma: no cover
64
63
  self._errors = []
65
64
  is_valid = self._is_valid()
66
65
  if self.errors and raise_exception:
67
- raise ParserValidationException(errors=self.errors)
66
+ raise ParserValidationError(errors=self.errors)
68
67
  return is_valid
69
68
 
70
69
  def _get_additional_information(self) -> dict[str, any]:
@@ -7,6 +7,7 @@ from wbcore.contrib.directory.serializers import (
7
7
  InternalUserProfileRepresentationSerializer,
8
8
  )
9
9
  from wbcore.contrib.documents.models import Document
10
+ from wbcore.contrib.i18n.serializers.mixins import ModelTranslateSerializerMixin
10
11
  from wbcore.contrib.tags.serializers import TagSerializerMixin
11
12
  from wbcore.serializers import (
12
13
  HyperlinkField,
@@ -58,9 +59,6 @@ class DependantArticleModelSerializer(ModelSerializer):
58
59
  )
59
60
 
60
61
 
61
- from wbcore.contrib.i18n.serializers.mixins import ModelTranslateSerializerMixin
62
-
63
-
64
62
  class ArticleModelSerializer(ModelTranslateSerializerMixin, TagSerializerMixin, ModelSerializer):
65
63
  """Serializes a subset of the fields of of the Article model to minimize
66
64
  workload on serializing lists of instances.
@@ -323,15 +321,13 @@ class ArticleFullModelSerializer(ArticleModelSerializer):
323
321
  if (
324
322
  self.instance
325
323
  and data.get("type")
326
- and data.get("type").slug != "ddq"
324
+ and data.get("type").allow_empty_author
327
325
  and (
328
326
  ("author" in data and not data.get("author"))
329
327
  or ("author" not in data and self.instance.author is None)
330
328
  )
331
329
  ):
332
- raise serializers.ValidationError(
333
- {"type": 'This must be set to "DDQ" as long as no author has been assigned!'}
334
- )
330
+ raise serializers.ValidationError({"type": f'The type {data["type"]} does not allow empty author.'})
335
331
 
336
332
  return data
337
333
 
@@ -1,6 +1,7 @@
1
1
  from django.dispatch import receiver
2
2
  from dynamic_preferences.registries import global_preferences_registry
3
3
  from wbcore.test.signals import custom_update_kwargs
4
+
4
5
  from wbwriter.models import Article
5
6
  from wbwriter.viewsets import ArticleModelViewSet, ReviewerArticleModelViewSet
6
7
 
@@ -1,6 +1,7 @@
1
1
  import pytest
2
2
  from rest_framework.test import APIRequestFactory
3
3
  from wbcore.contrib.authentication.factories import InternalUserFactory
4
+
4
5
  from wbwriter.factories import ArticleFactory, MetaInformationInstanceFactory
5
6
  from wbwriter.filters import MetaInformationInstanceFilter
6
7
  from wbwriter.models import MetaInformationInstance
@@ -5,6 +5,7 @@ from django.forms.models import model_to_dict
5
5
  from django_fsm import has_transition_perm
6
6
  from wbcore.contrib.authentication.factories import UserFactory
7
7
  from wbcore.contrib.authentication.models import Permission, User
8
+
8
9
  from wbwriter.factories import (
9
10
  ArticleFactory,
10
11
  ArticleTypeFactory,
@@ -422,7 +423,7 @@ class TestArticlePermissions:
422
423
  status=Article.Status.APPROVED, type=ArticleTypeFactory(label="Mid Year Review")
423
424
  )
424
425
  article_d: Article = ArticleFactory(
425
- status=Article.Status.APPROVED, type=ArticleTypeFactory(label="Unalligned Article")
426
+ status=Article.Status.APPROVED, type=ArticleTypeFactory(can_be_published=False, label="Unalligned Article")
426
427
  )
427
428
  assert article_a.can_be_published()
428
429
  assert article_b.can_be_published()
@@ -1,4 +1,5 @@
1
1
  import pytest
2
+
2
3
  from wbwriter.templatetags.writer import cite_article, table_of_contents
3
4
 
4
5
 
@@ -21,7 +21,6 @@ router.register(
21
21
 
22
22
  router.register(r"article", viewsets.ArticleModelViewSet, basename="article")
23
23
  router.register(r"articlerepresentation", viewsets.ArticleRepresentionViewSet, basename="articlerepresentation")
24
- # router.register(r"ddq", viewsets.DDQArticleModelViewSet, basename="ddq")
25
24
  router.register(r"review-article", viewsets.ReviewerArticleModelViewSet, basename="review-article")
26
25
  router.register(
27
26
  r"articletyperepresentation",
@@ -46,7 +46,7 @@ class ArticleModelButtonConfig(ButtonViewConfig):
46
46
  if (
47
47
  instance.status == Article.Status.DRAFT
48
48
  and instance.type
49
- and instance.type.slug == "ddq"
49
+ and instance.type.allow_empty_author
50
50
  and not instance.author
51
51
  ):
52
52
  custom_instance_buttons.add(bt.ActionButton(key="edit", label="Edit", icon=WBIcon.EDIT.icon))
@@ -15,15 +15,6 @@ WRITER_MENU = Menu(
15
15
  method=lambda request: is_internal_user(request.user), permissions=["wbwriter.view_article"]
16
16
  ),
17
17
  ),
18
- # MenuItem(
19
- # label="DDQ",
20
- # endpoint="wbwriter:ddq-list",
21
- # add=MenuItem(
22
- # label="Create a DDQ related entry",
23
- # endpoint="wbwriter:ddq-list",
24
- # ),
25
- # permission=ItemPermission(permissions=["wbwriter.view_article"]),
26
- # ),
27
18
  MenuItem(
28
19
  label="Articles to review",
29
20
  endpoint="wbwriter:review-article-list",
File without changes
File without changes
File without changes