diff --git a/scipost_django/journals/factories.py b/scipost_django/journals/factories.py
index 02689f39c69f5ca06a7c283590561a987f9e8ddc..9ddc58483d115e99e3f556a46c4247b5d994659e 100644
--- a/scipost_django/journals/factories.py
+++ b/scipost_django/journals/factories.py
@@ -8,23 +8,35 @@ from string import ascii_lowercase
 
 import factory
 import pytz
+
+from common.faker import LazyAwareDate, LazyRandEnum, fake
+
 from common.helpers import (
-    random_digits,
     random_external_doi,
     random_external_journal_abbrev,
 )
 from faker import Faker
+from funders.factories import FunderFactory, GrantFactory
 from journals.constants import (
+    CC_LICENSES,
     ISSUES_AND_VOLUMES,
     ISSUES_ONLY,
     JOURNAL_STRUCTURE,
     PUBLICATION_PUBLISHED,
 )
+from journals.models import journal
+from journals.models.autogenerated_file import AutogeneratedFileContentTemplate
+from journals.models.deposits import DOAJDeposit, Deposit, GenericDOIDeposit
+from journals.models.resource import PublicationResource
+from journals.models.submission_template import SubmissionTemplate
+from journals.models.update import PublicationUpdate
+from ontology.factories import SpecialtyFactory
 
 from .models import Issue, Journal, Publication, Reference, Volume
 
 
 class ReferenceFactory(factory.django.DjangoModelFactory):
+    publication = factory.SubFactory("journals.factories.JournalPublicationFactory")
     reference_number = factory.LazyAttribute(
         lambda o: o.publication.references.count() + 1
     )
@@ -48,14 +60,34 @@ class ReferenceFactory(factory.django.DjangoModelFactory):
 
 class JournalFactory(factory.django.DjangoModelFactory):
     college = factory.SubFactory("colleges.factories.CollegeFactory")
-    name = factory.Sequence(lambda n: "Fake Journal %s" % ascii_lowercase[n])
-    doi_label = factory.Sequence(lambda n: "SciPost%s" % ascii_lowercase[n])
-    issn = factory.lazy_attribute(lambda n: random_digits(8))
-    structure = factory.Iterator(JOURNAL_STRUCTURE, getter=lambda c: c[0])
+    name = factory.LazyAttributeSequence(
+        lambda self, n: f"SciPost {self.college.name} {n}"
+    )
+    name_abbrev = factory.LazyAttribute(
+        lambda self: "SciPost" + "".join([w[:2] for w in self.name.split()[1:]])
+    )
+    doi_label = factory.SelfAttribute("name_abbrev")
+    issn = factory.Faker("numerify", text="########")
+    structure = LazyRandEnum(JOURNAL_STRUCTURE)
+    list_order = factory.LazyAttribute(lambda self: self.college.journals.count() + 1)
+
+    active = True
+    submission_allowed = True
+
+    oneliner = factory.Faker("sentence")
+    description = factory.Faker("paragraph")
+    scope = factory.Faker("paragraph")
+    acceptance_criteria = factory.Faker("paragraph")
+    submission_insert = factory.Faker("paragraph")
+
+    template_latex_tgz = factory.django.FileField()
+    template_docx = factory.django.FileField()
+
+    cost_info = {"default": 500}
 
     class Meta:
         model = Journal
-        django_get_or_create = ("name",)
+        django_get_or_create = ("name", "doi_label")
 
     @classmethod
     def SciPostPhysics(cls):
@@ -73,16 +105,28 @@ class JournalFactory(factory.django.DjangoModelFactory):
             structure=ISSUES_ONLY,
         )
 
+    @factory.post_generation
+    def specialties(self, create, extracted, **kwargs):
+        if not create:
+            return
+        if extracted:
+            self.specialties.add(*extracted)
+        else:
+            specialties = SpecialtyFactory.create_batch(
+                3, acad_field=self.college.acad_field
+            )
+            self.specialties.add(*specialties)
+
 
 class VolumeFactory(factory.django.DjangoModelFactory):
     in_journal = factory.SubFactory(JournalFactory)
-    doi_label = factory.lazy_attribute(
-        lambda o: "%s.%i" % (o.in_journal.doi_label, o.number)
+    number = factory.LazyAttribute(lambda self: self.in_journal.volumes.count() + 1)
+    doi_label = factory.LazyAttribute(
+        lambda self: f"{self.in_journal.doi_label}.{self.number}"
     )
-    number = factory.lazy_attribute(lambda o: o.in_journal.volumes.count() + 1)
-    start_date = factory.Faker("date_time_this_decade")
-    until_date = factory.lazy_attribute(
-        lambda o: o.start_date + datetime.timedelta(weeks=26)
+    start_date = LazyAwareDate("date_time_this_decade")
+    until_date = factory.LazyAttribute(
+        lambda self: fake.aware.date_between(start_date=self.start_date, end_date="+1y")
     )
 
     class Meta:
@@ -91,103 +135,89 @@ class VolumeFactory(factory.django.DjangoModelFactory):
 
 
 class IssueFactory(factory.django.DjangoModelFactory):
-    in_volume = factory.SubFactory(VolumeFactory)
-    number = factory.LazyAttribute(lambda o: o.in_volume.issues.count() + 1)
+    class Meta:
+        model = Issue
+        abstract = True
+
+    class Params:
+        parent = None
+
+    number = factory.LazyAttribute(lambda self: self.parent.issues.count() + 1)
     doi_label = factory.LazyAttribute(
-        lambda o: "%s.%i" % (o.in_volume.doi_label, o.number)
+        lambda self: "%s.%i" % (self.parent.doi_label, self.number)
     )
+    slug = factory.LazyAttribute(
+        lambda self: "issue-" + self.doi_label.replace(".", "-")
+    )
+
+
+class VolumeIssueFactory(IssueFactory):
+    class Params:
+        parent = factory.SubFactory(VolumeFactory)
+
+    in_volume = factory.SelfAttribute("parent")
 
     start_date = factory.LazyAttribute(
-        lambda o: Faker().date_time_between(
-            start_date=o.in_volume.start_date,
-            end_date=o.in_volume.until_date,
+        lambda self: Faker().date_time_between(
+            start_date=self.parent.start_date,
+            end_date=self.parent.until_date,
             tzinfo=pytz.UTC,
         )
     )
     until_date = factory.LazyAttribute(
-        lambda o: o.start_date + datetime.timedelta(weeks=4)
+        lambda self: fake.aware.date_between(start_date=self.start_date, end_date="+1y")
     )
 
-    class Meta:
-        model = Issue
-        django_get_or_create = ("in_volume", "number")
-
-
-class PublicationFactory(factory.django.DjangoModelFactory):
-    accepted_submission = factory.SubFactory(
-        "submissions.factories.PublishedSubmissionFactory", generate_publication=False
+    path = factory.LazyAttribute(
+        lambda self: f"JOURNALS/{self.in_volume.in_journal.doi_label}/{self.in_volume.number}/{self.number}"
     )
-    paper_nr = 9999
-    pdf_file = factory.Faker("file_name", extension="pdf")
-    status = PUBLICATION_PUBLISHED
-    submission_date = factory.Faker("date_this_year")
-    acceptance_date = factory.Faker("date_this_year")
-    publication_date = factory.Faker("date_this_year")
 
-    acad_field = factory.SelfAttribute("accepted_submission.acad_field")
 
-    title = factory.SelfAttribute("accepted_submission.title")
-    abstract = factory.SelfAttribute("accepted_submission.abstract")
+class JournalIssueFactory(IssueFactory):
+    class Params:
+        parent = factory.SubFactory(JournalFactory, structure=ISSUES_ONLY)
 
-    # Dates
-    submission_date = factory.LazyAttribute(
-        lambda o: o.accepted_submission.submission_date
-    )
-    acceptance_date = factory.LazyAttribute(
-        lambda o: o.accepted_submission.latest_activity
-    )
-    publication_date = factory.LazyAttribute(
-        lambda o: o.accepted_submission.latest_activity
-    )
-    latest_activity = factory.LazyAttribute(
-        lambda o: o.accepted_submission.latest_activity
+    in_journal = factory.SelfAttribute("parent")
+
+    path = factory.LazyAttribute(
+        lambda self: f"JOURNALS/{self.in_journal.doi_label}/{self.number}"
     )
 
-    # Authors
-    author_list = factory.LazyAttribute(lambda o: o.accepted_submission.author_list)
 
+class BasePublicationFactory(factory.django.DjangoModelFactory):
     class Meta:
         model = Publication
+        abstract = True
         django_get_or_create = ("accepted_submission",)
-
-    class Params:
-        journal = None
+        exclude = ("pub_container",)
 
     @factory.lazy_attribute
-    def in_issue(self):
-        # Make sure Issues, Journals and doi are correct.
-        if self.journal:
-            journal = Journal.objects.get(doi_label=self.journal)
-        else:
-            journal = Journal.objects.order_by("?").first()
+    def pub_container(self):
+        issue = getattr(self, "in_issue", None)
+        journal = getattr(self, "in_journal", None)
+        return issue or journal
 
-        if journal.has_issues:
-            return Issue.objects.for_journal(journal.name).order_by("?").first()
-        return None
+    # Publication data
+    # TODO: This should be a PublishedSubmissionFactory
+    accepted_submission = factory.SubFactory(
+        "submissions.factories.SubmissionFactory"  # , generate_publication=False
+    )
+    status = PUBLICATION_PUBLISHED
 
     @factory.lazy_attribute
-    def in_journal(self):
-        # Make sure Issues, Journals and doi are correct.
-        if self.journal:
-            journal = Journal.objects.get(doi_label=self.journal)
-        elif not self.in_issue:
-            journal = (
-                Journal.objects.has_individual_publications().order_by("?").first()
-            )
-        else:
-            return None
+    def paper_nr(self):
+        if self.pub_container:
+            return self.pub_container.publications.count() + 1
 
-        if not journal.has_issues:
-            # Keep this logic in case self.journal is set.
-            return journal
-        return None
+    # Core fields
+    title = factory.SelfAttribute("accepted_submission.title")
+    author_list = factory.SelfAttribute("accepted_submission.author_list")
+    abstract = factory.SelfAttribute("accepted_submission.abstract")
+    pdf_file = factory.django.FileField()
 
-    @factory.lazy_attribute
-    def paper_nr(self):
-        if self.in_issue:
-            return self.in_issue.publications.count() + 1
-        elif self.in_journal:
-            return self.in_journal.publications.count() + 1
+    # Ontology-based semantic linking
+    acad_field = factory.SelfAttribute("accepted_submission.acad_field")
+    approaches = factory.SelfAttribute("accepted_submission.approaches")
 
     @factory.post_generation
     def specialties(self, create, extracted, **kwargs):
@@ -196,43 +226,193 @@ class PublicationFactory(factory.django.DjangoModelFactory):
         self.specialties.add(*self.accepted_submission.specialties.all())
 
     @factory.post_generation
-    def approaches(self, create, extracted, **kwargs):
+    def topics(self, create, extracted, **kwargs):
         if not create:
             return
-        self.approaches = self.accepted_submission.approaches
+        self.topics.add(*self.accepted_submission.topics.all())
 
-    @factory.lazy_attribute
-    def doi_label(self):
-        if self.in_issue:
-            return self.in_issue.doi_label + "." + str(self.paper_nr).rjust(3, "0")
-        elif self.in_journal:
-            return "%s.%i" % (self.in_journal.doi_label, self.paper_nr)
+    cc_license = LazyRandEnum(CC_LICENSES)
 
+    # Funders
     @factory.post_generation
-    def generate_publication(self, create, extracted, **kwargs):
-        if create and extracted is not False:
+    def grants(self, create, extracted, **kwargs):
+        if not create:
             return
+        if extracted:
+            self.grants.add(*extracted)
 
-        from journals.factories import PublicationFactory
-
-        factory.RelatedFactory(
-            PublicationFactory,
-            "accepted_submission",
-            title=self.title,
-            author_list=self.author_list,
-        )
+        grants = GrantFactory.create_batch(3)
+        self.grants.add(*grants)
 
     @factory.post_generation
-    def author_relations(self, create, extracted, **kwargs):
+    def funders_generic(self, create, extracted, **kwargs):
         if not create:
             return
+        if extracted:
+            self.funders_generic.add(*extracted)
+
+        funders_generic = FunderFactory.create_batch(3)
+        self.funders_generic.add(*funders_generic)
+
+    # Metadata
+    metadata = {}
+    metadata_xml = ""
+    metadata_DOAJ = {}
+    citedby = {}
+    number_of_citations = 0
+
+    @factory.lazy_attribute
+    def doi_label(self):
+        if self.pub_container:
+            return self.pub_container.doi_label + "." + str(self.paper_nr).rjust(3, "0")
+
+    # Date fields
+    submission_date = factory.SelfAttribute("accepted_submission.submission_date")
+    acceptance_date = factory.SelfAttribute("accepted_submission.latest_activity")
+    publication_date = factory.SelfAttribute("accepted_submission.latest_activity")
+    latest_activity = factory.SelfAttribute("accepted_submission.latest_activity")
+    latest_citedby_update = factory.SelfAttribute("accepted_submission.latest_activity")
+    latest_metadata_update = factory.SelfAttribute(
+        "accepted_submission.latest_activity"
+    )
+
+    # @factory.post_generation
+    # def generate_publication(self, create, extracted, **kwargs):
+    #     if create and extracted is not False:
+    #         return
+
+    #     from journals.factories import PublicationFactory
+
+    #     factory.RelatedFactory(
+    #         PublicationFactory,
+    #         "accepted_submission",
+    #         title=self.title,
+    #         author_list=self.author_list,
+    #     )
+
+    # @factory.post_generation
+    # def author_relations(self, create, extracted, **kwargs):
+    #     if not create:
+    #         return
+
+    #     # Append references
+    #     for i in range(5):
+    #         ReferenceFactory(publication=self)
 
-        # Append references
-        for i in range(5):
-            ReferenceFactory(publication=self)
+    # Copy author data from Submission
+    # for author in self.accepted_submission.authors.all():
+    #     self.authors.create(publication=self, profile=author)
+    # self.authors_claims.add(*self.accepted_submission.authors_claims.all())
+    # self.authors_false_claims.add(*self.accepted_submission.authors_false_claims.all())
+
+
+class JournalPublicationFactory(BasePublicationFactory):
+    in_journal = factory.SubFactory(JournalFactory)
+
+
+class VolumeIssuePublicationFactory(BasePublicationFactory):
+    in_issue = factory.SubFactory(VolumeIssueFactory)
+    in_journal = factory.SelfAttribute("in_issue.in_volume.in_journal")
+
+
+class JournalIssuePublicationFactory(BasePublicationFactory):
+    in_issue = factory.SubFactory(JournalIssueFactory)
+    in_journal = factory.SelfAttribute("in_issue.in_journal")
+
+
+class AutogeneratedFileContentTemplateFactory(factory.django.DjangoModelFactory):
+    class Meta:
+        model = AutogeneratedFileContentTemplate
+
+    journal = factory.SubFactory(JournalFactory)
+    name = factory.Faker("word")
+    description = factory.Faker("sentence", nb_words=3)
+    content_template_string = factory.Faker("word")
+
+
+class DepositFactory(factory.django.DjangoModelFactory):
+    class Meta:
+        model = Deposit
 
-        # Copy author data from Submission
-        # for author in self.accepted_submission.authors.all():
-        #     self.authors.create(publication=self, profile=author)
-        # self.authors_claims.add(*self.accepted_submission.authors_claims.all())
-        # self.authors_false_claims.add(*self.accepted_submission.authors_false_claims.all())
+    publication = factory.SubFactory(JournalPublicationFactory)
+    deposition_date = factory.SelfAttribute("publication.publication_date")
+    timestamp = factory.LazyAttribute(
+        lambda self: self.deposition_date.strftime("%Y%m%d%H%M%S")
+    )
+    deposit_successful = True
+    response_text = ""
+    doi_batch_id = factory.Faker("word")
+    metadata_xml = ""
+    metadata_xml_file = factory.django.FileField()
+    response_text = ""
+
+
+class DOAJDepositFactory(factory.django.DjangoModelFactory):
+    class Meta:
+        model = DOAJDeposit
+
+    publication = factory.SubFactory(JournalPublicationFactory)
+    deposition_date = factory.SelfAttribute("publication.publication_date")
+    timestamp = factory.LazyAttribute(
+        lambda self: self.deposition_date.strftime("%Y%m%d%H%M%S")
+    )
+    deposit_successful = True
+    metadata_DOAJ = {}
+    metadata_DOAJ_file = factory.django.FileField()
+    response_text = ""
+
+
+class GenericDOIDepositFactory(factory.django.DjangoModelFactory):
+    class Meta:
+        model = GenericDOIDeposit
+        abstract = True
+
+    deposition_date = LazyAwareDate("date_this_year")
+    timestamp = factory.LazyAttribute(
+        lambda self: self.deposition_date.strftime("%Y%m%d%H%M%S")
+    )
+    deposit_successful = True
+    doi_batch_id = factory.Faker("word")
+    metadata_xml = ""
+    response = ""
+
+
+class PublicationResourceFactory(factory.django.DjangoModelFactory):
+    class Meta:
+        model = PublicationResource
+
+    _type = LazyRandEnum(PublicationResource.TYPE_CHOICES)
+    publication = factory.SubFactory(JournalPublicationFactory)
+    url = factory.Faker("uri")
+    comments = factory.Faker("sentence")
+    deprecated = False
+
+
+class SubmissionTemplateFactory(factory.django.DjangoModelFactory):
+    class Meta:
+        model = SubmissionTemplate
+
+    template_type = LazyRandEnum(SubmissionTemplate.TYPE_CHOICES)
+    template_file = factory.django.FileField()
+    journal = factory.SubFactory(JournalFactory)
+    date = LazyAwareDate("date_this_year")
+    instructions = factory.Faker("paragraph")
+
+
+class PublicationUpdateFactory(factory.django.DjangoModelFactory):
+    class Meta:
+        model = PublicationUpdate
+
+    publication = factory.SubFactory(JournalPublicationFactory)
+    number = factory.LazyAttribute(lambda self: self.publication.updates.count() + 1)
+    update_type = LazyRandEnum(PublicationUpdate.TYPE_CHOICES)
+    text = factory.Faker("paragraph")
+    publication_date = factory.LazyAttribute(
+        lambda self: fake.aware.date_between(
+            start_date=self.publication.publication_date, end_date="+1y"
+        )
+    )
+    doideposit_needs_updating = False
+    doi_label = factory.LazyAttribute(
+        lambda self: f"{self.publication.doi_label}.Upd.{self.number}"
+    )
diff --git a/scipost_django/journals/tests/test_factories.py b/scipost_django/journals/tests/test_factories.py
new file mode 100644
index 0000000000000000000000000000000000000000..29088a64be244ef4db8d336169ef66019fefd043
--- /dev/null
+++ b/scipost_django/journals/tests/test_factories.py
@@ -0,0 +1,122 @@
+__copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
+__license__ = "AGPL v3"
+
+from django.test import TestCase
+from ..factories import (
+    AutogeneratedFileContentTemplateFactory,
+    DOAJDepositFactory,
+    DepositFactory,
+    JournalIssueFactory,
+    JournalIssuePublicationFactory,
+    PublicationResourceFactory,
+    PublicationUpdateFactory,
+    SubmissionTemplateFactory,
+    VolumeIssuePublicationFactory,
+    JournalFactory,
+    JournalPublicationFactory,
+    ReferenceFactory,
+    VolumeFactory,
+    VolumeIssueFactory,
+)
+
+
+class TestJournalFactory(TestCase):
+    def test_can_create_journals(self):
+        journal = JournalFactory()
+        self.assertIsNotNone(journal)
+
+    def test_default_journal_factory_has_college_specialties(self):
+        journal = JournalFactory()
+        college_specialties = journal.college.specialties.all()
+        journal_specialties = journal.specialties.all()
+        self.assertQuerysetEqual(college_specialties, journal_specialties)
+
+    def test_can_create_scipost_phys(self):
+        scipost_phys = JournalFactory.SciPostPhysics()
+        self.assertEqual(scipost_phys.name, "SciPost Physics")
+        self.assertEqual(scipost_phys.doi_label, "SciPostPhys")
+
+
+class TestReferenceFactory(TestCase):
+    def test_can_create_references(self):
+        reference = ReferenceFactory()
+        self.assertIsNotNone(reference)
+
+
+class TestVolumeFactory(TestCase):
+    def test_can_create_volumes(self):
+        volume = VolumeFactory()
+        self.assertIsNotNone(volume)
+
+
+class TestVolumeIssueFactory(TestCase):
+    def test_can_create_volume_issues(self):
+        volume_issue = VolumeIssueFactory()
+        self.assertIsNotNone(volume_issue)
+
+
+class TestJournalIssueFactory(TestCase):
+    def test_can_create_journal_issues(self):
+        journal_issue = JournalIssueFactory()
+        self.assertIsNotNone(journal_issue)
+
+
+class TestJournalPublicationFactory(TestCase):
+    def test_can_create_journal_publications(self):
+        journal_publication = JournalPublicationFactory()
+        self.assertIsNotNone(journal_publication)
+
+    def test_can_create_scipostphys_publications(self):
+        publication = JournalPublicationFactory(
+            in_journal=JournalFactory.SciPostPhysics()
+        )
+        self.assertIsNotNone(publication)
+        self.assertEqual(publication.in_journal.name, "SciPost Physics")
+
+
+class TestVolumeIssuePublicationFactory(TestCase):
+    def test_can_create_volume_issue_publications(self):
+        volume_issue_publication = VolumeIssuePublicationFactory()
+        self.assertIsNotNone(volume_issue_publication)
+
+
+class TestJournalIssuePublicationFactory(TestCase):
+    def test_can_create_journal_issue_publications(self):
+        journal_issue_publication = JournalIssuePublicationFactory()
+        self.assertIsNotNone(journal_issue_publication)
+
+
+class TestAutogeneratedFileContentTemplateFactory(TestCase):
+    def test_can_create_autogen_file_content_templates(self):
+        autogen_file_content_template = AutogeneratedFileContentTemplateFactory()
+        self.assertIsNotNone(autogen_file_content_template)
+
+
+class TestDepositFactory(TestCase):
+    def test_can_create_deposits(self):
+        deposit = DepositFactory()
+        self.assertIsNotNone(deposit)
+
+
+class TestDOAJDepositFactory(TestCase):
+    def test_can_create_doaj_deposits(self):
+        doaj_deposit = DOAJDepositFactory()
+        self.assertIsNotNone(doaj_deposit)
+
+
+class TestPublicationResourceFactory(TestCase):
+    def test_can_create_publication_resources(self):
+        publication_resource = PublicationResourceFactory()
+        self.assertIsNotNone(publication_resource)
+
+
+class TestSubmissionTemplateFactory(TestCase):
+    def test_can_create_submission_templates(self):
+        submission_template = SubmissionTemplateFactory()
+        self.assertIsNotNone(submission_template)
+
+
+class TestPublicationUpdateFactory(TestCase):
+    def test_can_create_publication_updates(self):
+        publication_update = PublicationUpdateFactory()
+        self.assertIsNotNone(publication_update)