I have 2 different views that seem to work on their own. But when I try to use them together with a http redirect then that fails.
The context is pretty straightforward, I have a view that creates an object and another view that updates this object, both with the same form.
The only thing that is a bit different is that we use multiple sites. So we check if the site that wants to update the object is the site that created it. If yes then it does a normal update of the object. If no (that's the part that does not work here) then I http redirect the update view to the create view and I pass along the object so the new site can create a new object based on those initial values.
Here is the test to create a new resource (passes successfully) :
@pytest.mark.resource_create
@pytest.mark.django_db
def test_create_new_resource_and_redirect(client):
data = {
"title": "a title",
"subtitle": "a sub title",
"status": 0,
"summary": "a summary",
"tags": "#tag",
"content": "this is some updated content",
}
with login(client, groups=["example_com_staff"]):
response = client.post(reverse("resources-resource-create"), data=data)
resource = models.Resource.on_site.all()[0]
assert resource.content == data["content"]
assert response.status_code == 302
Here is the test to create a new resource from an existing object (passes successfully) :
@pytest.mark.resource_create
@pytest.mark.django_db
def test_create_new_resource_from_pushed_resource_and_redirect(request, client):
existing_resource = baker.make(models.Resource)
other_site = baker.make(Site)
existing_resource.site_origin = other_site
existing_resource.sites.add(other_site)
our_site = get_current_site(request)
existing_resource.sites.add(our_site)
original_content = "this is some original content"
existing_resource.content = original_content
existing_resource.save()
data = {
"title": "a title",
"subtitle": "a sub title",
"status": 0,
"summary": "a summary",
"tags": "#tag",
"content": "this is some updated content",
}
url = reverse("resources-resource-create-from-shared", args=[existing_resource.id])
with login(client, groups=["example_com_staff"]):
response = client.post(url, data=data)
assert response.status_code == 302
existing_resource.refresh_from_db()
assert existing_resource.content == original_content
assert our_site not in existing_resource.sites.all()
new_resource = models.Resource.on_site.get()
assert new_resource.content == data["content"]
Here is the create view :
@login_required
def resource_create(request, pushed_resource_id=None):
"""
Create new resource
In case of a resource that is pushed from a different site
create a new resource based on the pushed one.
"""
has_perm_or_403(request.user, "sites.manage_resources", request.site)
try:
pushed_resource = models.Resource.objects.get(id=pushed_resource_id)
pushed_resource_as_dict = model_to_dict(pushed_resource)
initial_data = pushed_resource_as_dict
except ObjectDoesNotExist:
pushed_resource = None
initial_data = None
if request.method == "POST":
form = EditResourceForm(request.POST, initial=initial_data)
if form.is_valid():
resource = form.save(commit=False)
resource.created_by = request.user
with reversion.create_revision():
reversion.set_user(request.user)
resource.save()
resource.sites.add(request.site)
if pushed_resource:
pushed_resource.sites.remove(request.site)
pushed_resource.save()
resource.site_origin = request.site
resource.save()
form.save_m2m()
next_url = reverse("resources-resource-detail", args=[resource.id])
return redirect(next_url)
else:
form = EditResourceForm()
return render(request, "resources/resource/create.html", locals())
Here is the test to update the resource from the original site (passes successfully) :
@pytest.mark.resource_update
@pytest.mark.django_db
def test_update_resource_from_origin_site_and_redirect(request, client):
resource = baker.make(models.Resource)
our_site = get_current_site(request)
resource.site_origin = our_site
resource.save()
previous_update = resource.updated_on
url = reverse("resources-resource-update", args=[resource.id])
data = {
"title": "a title",
"subtitle": "a sub title",
"status": 0,
"summary": "a summary",
"tags": "#tag",
"content": "this is some updated content",
}
with login(client, groups=["example_com_staff"]):
response = client.post(url, data=data)
assert response.status_code == 302
resource.refresh_from_db()
assert resource.content == data["content"]
assert resource.updated_on > previous_update
And finally the test to update from a different site that should create a new resource from the original one (that one fails):
@pytest.mark.resource_update
@pytest.mark.django_db
def test_update_resource_from_non_origin_site_and_redirect(request, client):
original_resource = baker.make(models.Resource)
our_site = get_current_site(request)
other_site = baker.make(Site)
original_resource.sites.add(our_site, other_site)
original_resource.site_origin = other_site
previous_update = original_resource.updated_on
original_content = "this is some original content"
original_resource.content = original_content
original_resource.save()
assert models.Resource.on_site.all().count() == 1
url = reverse("resources-resource-update", args=[original_resource.id])
updated_data = {
"title": "a title",
"subtitle": "a sub title",
"status": 0,
"summary": "a summary",
"tags": "#tag",
"content": "this is some updated content",
}
with login(client, groups=["example_com_staff"]):
response = client.post(url, data=updated_data)
assert response.status_code == 302
original_resource.refresh_from_db()
assert original_resource.content == original_content
assert original_resource.updated_on == previous_update
assert other_site in original_resource.sites.all()
assert our_site not in original_resource.sites.all()
assert models.Resource.on_site.all().count() == 1
new_resource = models.Resource.on_site.get()
assert new_resource.content == updated_data["content"]
assert other_site not in new_resource.sites.all()
assert our_site in new_resource.sites.all()
What happens is that no new object gets created here and the original object is modified instead.
Here is the update view :
@login_required
def resource_update(request, resource_id=None):
"""Update informations for resource"""
has_perm_or_403(request.user, "sites.manage_resources", request.site)
resource = get_object_or_404(models.Resource, pk=resource_id)
if resource.site_origin is not None and resource.site_origin != request.site:
pushed_resource_id = resource.id
next_url = reverse("resources-resource-create-from-shared",
args=[pushed_resource_id]
)
return redirect(next_url)
next_url = reverse("resources-resource-detail", args=[resource.id])
if request.method == "POST":
form = EditResourceForm(request.POST, instance=resource)
if form.is_valid():
resource = form.save(commit=False)
resource.updated_on = timezone.now()
with reversion.create_revision():
reversion.set_user(request.user)
resource.save()
form.save_m2m()
return redirect(next_url)
else:
form = EditResourceForm(instance=resource)
return render(request, "resources/resource/update.html", locals())
And the model form :
class EditResourceForm(forms.ModelForm):
"""Create and update form for resources"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Queryset needs to be here since on_site is dynamic and form is read too soon
self.fields["category"] = forms.ModelChoiceField(
queryset=models.Category.on_site.all(),
empty_label="(Aucune)",
required=False,
)
self.fields["contacts"] = forms.ModelMultipleChoiceField(
queryset=addressbook_models.Contact.on_site.all(),
required=False,
)
# Try to load the Markdown template into 'content' field
try:
tmpl = get_template(
template_name="resources/resource/create_md_template.md"
)
self.fields["content"].initial = tmpl.render()
except TemplateDoesNotExist:
pass
content = MarkdownxFormField(label="Contenu")
title = forms.CharField(
label="Titre", widget=forms.TextInput(attrs={"class": "form-control"})
)
subtitle = forms.CharField(
label="Sous-Titre",
widget=forms.TextInput(attrs={"class": "form-control"}),
required=False,
)
summary = forms.CharField(
label="Résumé bref",
widget=forms.Textarea(
attrs={"class": "form-control", "rows": "3", "maxlength": 400}
),
required=False,
)
class Meta:
model = models.Resource
fields = [
"title",
"status",
"subtitle",
"summary",
"tags",
"category",
"departments",
"content",
"contacts",
"expires_on",
]
Any idea about what I did wrong is welcome. And if you think a better strategy should be employed then feel free to comment.
My bad. I use initial=initial_data
in the POST
part of the create
view. Which makes no sense.
When moving the initial=initial_data
to the GET
part then it works.
The test_update_resource_from_non_origin_site_and_redirect
test still fails though. I'm going to investigate since the feature works fine from within the web interface.