On my generic class based CreateView, I would like to perform an instruction which creates new objects from another model related to the current created object.
Example:
Collection
is a model which is related to 3 differentsElement
objects. When I create aMyCollection
object, which is related to aCollection
and aUser
, 3MyElement
object must be created too and related to the newly createdMyCollection
object. One for eachElement
related toCollection
.
# models.py
from django.contrib.auth.models import User
from django.db import models
class Collection(models.Model):
# attributes and methods...
class Element(models.Model):
collection = models.ForeignKey(Collection, # more arguments...)
# more attributes and methods
class MyCollection(models.Model):
user = models.ForeignKey(User, # more arguments...)
collection = = models.ForeignKey(Collection, # more arguments...)
# more attributes and methods
def get_absolute_url(self):
reverse(# some url with the object pk)
class MyElement(models.Model):
element = models.ForeignKey(Element, # more arguments...)
my_collection = models.ForeignKey(MyCollection, # more arguments...)
# more attributes and methods
I'm using the generic CreateView from Django and after a little bit of research, I saw that it was possible to perform additional actions in the CreateView by overriding the get_success_url() method.
In my example, I did something similar:
# views.py
from django.views.generic import CreateView
from .utils import create_myelements_for_mycollection
class MyCollectionCreateView(CreateView):
model = MyCollection
# more attributes and methods...
def get_success_url(self):
create_myelements_for_mycollection(self.get_object()) # Here is where the bug occurs, works fine without this line
return super().get_success_url()
# utils.py
from .models import Collection, Element, MyCollection, MyElement
def create_myelements_for_mycollection(my_collection):
for element in my_collection.collection.elements_set.all():
MyElement.objects.create(
element=element,
my_collection=my_collection,
)
# urls.py
from django.urls import re_path
from . import views
urlpatterns = [
re_path(
r"^myelement/l/$",
views.MyElementListView.as_view(),
),
re_path(
r"^myelement/r/(?P<pk>[0-9]+)/$",
views.MyElementDetailView.as_view(),
),
re_path(
r"^myelement/c/(?P<element_pk>\d+)/$",
views.MyElementCreateView.as_view(),
),
re_path(
r"^mycollection/l/$",
views.MyCollectionListView.as_view(),
),
re_path(
r"^mycollection/r/(?P<pk>[0-9]+)/$",
views.MyCollectionDetailView.as_view(),
),
re_path(
r"^mycollection/c/(?P<collection_pk>\d+)/$",
views.MyCollectionCreateView.as_view(),
),
]
When I create a new MyCollection
object, all the MyElements
are successfully created in the database but I got this error:
AttributeError: Generic detail view MyCollectionCreateView must be called with either an object pk or a slug in the URLconf.
I don't understand why.
My CreateView url doesn't have any pk because when you create a new object, it doesn't have a pk yet. Also, MyCollection
has it's own get_absolute_url
. Without that specific instruction, I works fine.
Can someone explain me what causes the error and if there is a better way to perform an instruction like this after the object creation ?
Thank you for your help.
FYI:
Django 3.1
Pyhton 3.8
EDIT
I tried to use a post_save signal instead (which is actually way cleaner to write), but I'm stuck with exactly the same error.
# models.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from .utils import create_myelements_for_mycollection
# ...models
@receiver(post_save, sender=MyCollection)
def create_myelements_for_mycollection(sender, instance, **kwargs):
if instance is created: # ? (Didn't look at how to write this condition in a signal yet)
create_myelements_for_mycollection(my_collection)
AttributeError: Generic detail view MyCollectionCreateView must be called with either an object pk or a slug in the URLconf.
EDIT 2
By removing the get_absolute_url()
override from the MyCollectionCreateView
class, it actually works. It's great but I would still like to know what the issue was. Probably something stupid I didn't see, if this solved the issue.
# models.py
# ... previous code
class MyCollectionCreateView(CreateView):
model = MyCollection
# more attributes and methods...
# Removing this solved the issue
# def get_success_url(self):
# create_myelements_for_mycollection(self.get_object())
#
# return super().get_success_url()
# more code ...
get_absolute_url()
is used when a new instance is made for a model, as django must know where to go when a new post is created or a new instance is created.
From the error
AttributeError: Generic detail view MyCollectionCreateView must be called with either an object pk or a slug in the URLconf.
It tells you that it must be called with an object pk
or a slug
, as such your construction of the URL is where the problem lies. I am not sure if the method create_myelements_for_mycollection()
caters that need.
Ideally what you need is something like, e.g.
def get_absolute_url(self):
return f"/mycollection/{self.slug}/"
to generate the URL
in the above pattern to be used for example in an anchor tag.