Testing related page in Wagtail - django

I've got a ContentPage model in wagtail and a RelatedPost model that links other ContentPage models to ContentPage a bit like this:
class ContentPage(Page):
summary = RichTextField(blank=True)
body = RichTextField(blank=True)
published = models.DateTimeField(default=timezone.now())
content_panels = Page.content_panels + [
FieldPanel('summary'),
FieldPanel('body', classname="full"),
InlinePanel('related_page', label="Related Content"),
]
settings_panels = Page.settings_panels + [
FieldPanel('published'),
]
class RelatedPost(Orderable):
post = ParentalKey(
'ContentPage',
related_name='related_page'
)
page = models.ForeignKey(
'ContentPage',
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="+"
)
panels = [
FieldPanel('page')
]
When I run this test:
class ContentPageTests(WagtailPageTests):
def test_can_create_article_page(self):
self.assertCanCreateAt(ContentIndexPage, ContentPage)
# content_index is just a parent page
content_index = self.create_content_index_page()
self.assertCanCreate(content_index, ContentPage, {
'title': 'Test Article',
'published': datetime.datetime.now()
})
I get an error saying:
django.core.exceptions.ValidationError: ['ManagementForm data is missing or has been tampered with']
The admin works fine. I can save related pages etc and when I comment out the InlinePanel line it works fine.

The data passed to assertCanCreate needs to match the format of a form submission being posted to the 'edit page' form in the Wagtail admin. For a child model in an InlinePanel, Wagtail handles this with a Django formset - see https://docs.djangoproject.com/en/1.10/topics/forms/formsets/#formset-validation - and so you need to supply all the fields that the Django formset logic would expect, including the management form. The simplest case that passes validation is a management form that simply reports that there are no child forms:
self.assertCanCreate(content_index, ContentPage, {
'title': 'Test Article',
'published': datetime.datetime.now(),
'related_page-TOTAL_FORMS': 0,
'related_page-INITIAL_FORMS': 0,
'related_page-MAX_NUM_FORMS': 999,
})

Related

Testing Django Wagtail - assert that a child of the given Page type can be created under the parent, using the supplied POST data

I've defined a custom page model (a blog post) as a child of a parent model (a blog index page) and I want to test that the child can be created under its parent.
The BlogPage and BlogIndexPage models are copied from the wagtail "basic blog" example in the documentation, and works as expected.
I'm trying to follow the documentation but I get the following validation error:
AssertionError: Validation errors found when creating a cms.blogpage:
E date:
E This field is required.
E intro:
E This field is required.
E slug:
E This field is required.
E title:
E This field is required.
I suspect that I'm defining my fixture incorrectly, but I am not what the correct form is. Any help is greatly appreciated! Can someone explain why it isn't working?
fixture (apps.cms.tests.fixtures.blogPage.json):
[
{
"model":"wagtailcore.page",
"pk": 1,
"fields":{
"date":"2022-02-28",
"intro":"intro to the post...",
"slug":"slug/",
"title":"the title",
"body":"body of the post...",
"categories":[
1
],
"content_type": ["cms", "blogpage"],
"depth": 2
}
},
{
"model": "cms.blogpage",
"pk": 1,
"fields": {}
}
]
the test class (apps.cms.tests.test_pages.py):
class MyPageTests(WagtailPageTests):
def setUp(self):
self.login()
page = BlogIndexPage(title="Home page", slug="home", path="foo", depth=1)
page.save()
def test_create_blog_post(self):
cwd = Path.cwd()
root_page = BlogIndexPage.objects.first()
with open(f"{cwd}/lettergun/apps/cms/tests/fixtures/BlogPage.json") as json_file:
fixture = json.load(json_file)
# Assert that a ContentPage can be made here, with this POST data
self.assertCanCreate(root_page, BlogPage, nested_form_data(fixture))
the models (apps.cms.models.py):
class BlogIndexPage(Page):
template = "blog.html"
intro = models.TextField(blank=True)
def get_context(self, request):
# Update context to include only published posts, ordered by reverse-chron
context = super().get_context(request)
blogpages = self.get_children().live().order_by("-first_published_at")
context["blogpages"] = blogpages
return context
content_panels = Page.content_panels + [FieldPanel("intro", classname="full")]
class BlogPageTag(TaggedItemBase):
content_object = ParentalKey("BlogPage", related_name="tagged_items", on_delete=models.CASCADE)
class BlogPage(Page):
template = "blog-post.html"
date = models.DateField("Post date")
intro = models.CharField(max_length=250)
body = RichTextField(blank=True)
tags = ClusterTaggableManager(through=BlogPageTag, blank=True)
categories = ParentalManyToManyField("cms.BlogCategory", blank=True)
def main_image(self):
gallery_item = self.gallery_images.first()
if gallery_item:
return gallery_item.image
else:
return None
search_fields = Page.search_fields + [
index.SearchField("intro"),
index.SearchField("body"),
]
content_panels = Page.content_panels + [
MultiFieldPanel(
[
FieldPanel("date"),
FieldPanel("tags"),
FieldPanel("categories", widget=forms.CheckboxSelectMultiple),
],
heading="Blog information",
),
FieldPanel("intro"),
FieldPanel("body"),
InlinePanel("gallery_images", label="Gallery images"),
]
The last argument to self.assertCanCreate is a dictionary of HTTP POST data to be submitted to the 'create page' admin view. This is an entirely different thing to a fixture (which is a representation of the page data as stored in the database), and the data structures are not compatible.
At its simplest, the POST dictionary can just consist of the required fields date, intro, slug and title:
self.assertCanCreate(root_page, BlogPage, {
'date': '2022-02-28',
'intro': "intro to the post...",
'slug': 'my-blog-page',
'title': 'My blog page',
})
The nested_form_data helper is only needed if your test is creating a page with data that's more complex than a flat list of fields - for example, if you wanted your page data to contain some gallery images, you'd need to use it along with the inline_formset helper. As you have an InlinePanel on your page, you need to account for that even if you're not passing it any data - see https://stackoverflow.com/a/71356332/1853523 for details.

Django REST framework RetrieveAPIView gets empty "id" parameter and returns 404 error

I use Django 1.11 + Django REST Framework. I'm trying to get detailed data about distinct record using generic RetrieveAPIView from Django REST Framework and server returns "HTTP 404 Not Found" result, i.e. no data.
models.py file:
class TestPage( models.Model):
title = models.CharField( size = 120)
counter = models.IntegerField( default = 0)
def __unicode__(self):
return u'' + self.title
serializers.py file:
class TestPageSerializer(serializers.ModelSerializer):
class Meta:
fields = ('id', 'title', 'counter',)
model = models.TestPage
urls.py file:
urlpatterns = [
url( r'^admin/', admin.site.urls),
url( r'^api/', include( 'rest_framework.urls')),
url( r'^api/', include( 'testpages.urls')),
]
testpages/urls.py file:
urlpatterns = [
url( r'^$', views.TestPageList.as_view()),
url( r'^(?P<id>)\d+/$', views.TestPageDetail.as_view()),
]
and at last views.py file:
class TestPageList(generics.ListAPIView):
lookup_field = 'id'
queryset = TestPage.objects.all()
serializer_class = TestPageSerializer
class TestPageDetail(generics.RetrieveAPIView):
lookup_field = 'id'
queryset = TestPage.objects.all()
serializer_class = TestPageSerializer
# def get_object( self):
# print self.kwargs
If I request "http://127.0.0.1:8000/api/" for all records in the list, server returns all records. But if I want to get record by id using "http://127.0.0.1:8000/api/1/" for example, I get the empty result like in the photo below.
[![enter image description here][1]][1]
If I uncomment get_object()-function in last 2 lines, I can see {'id': u''} in terminal i.e. server gets empty parameter 'id'.
What wrong with this code?
The issue is your regular expression matching for id, you're putting \d+ outside of the matching group when it's actually what you want to match:
url(r'^(?P<id>\d+)/$', views.TestPageDetail.as_view())
By the way, to be good REST citizen, you shouldn't add the / at the end of a URL for a specific object.
Note: As mentioned by JPG, adding a name for the resource you're fetching (plural) would be proper RESTful:
url(r'^pages/(?P<id>\d+)$', views.TestPageDetail.as_view())
This way, you fetch page nr. 1 at /api/pages/1
The original problem was the closing parathesis (the regex expression), you were misplaced that.
urlpatterns = [
url(r'^$', views.TestPageList.as_view()),
url(r'^(?P<id>\d+)/$', views.TestPageDetail.as_view()),
^^^ here
]
Apart from that, You should change the urls patterns to a sensible form, as
#testpages/urls.py
urlpatterns = [
url(r'^test/$', views.TestPageList.as_view()),
url(r'^test/(?P<id>\d+)/$', views.TestPageDetail.as_view()),
]
then the list-api will be available on /api/test/ endpoint and the detail-api will be available on /api/test/{id}/

Wagtail API - show image URL on json output

Fairly new to Wagtail - I'm currently creating a Wagtail API for my React app. Have installed successfully and am getting a json output, but not getting a url for images that are uploaded in the Wagtail admin panel. I have searched online, but not having much joy.
This is the basic home page model I have created
class BarsHomePage(Previewable, Themable, Page):
bars_site_homepage_test = models.CharField(max_length=255, blank=True)
feed_image = models.ForeignKey(
'DemoImage',
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name='+'
)
api_fields = ['bars_site_homepage_test','feed_image']
class DemoImage(Image):
#property
def fullwidth_url(self):
return generate_image_url(self, 'width-800')
#property
def halfwidth_url(self):
return generate_image_url(self, 'width-400')
api_fields = (
'fullwidth_url',
'halfwidth_url',
)
class Meta:
proxy = True
Json output
{
"id": 504,
"meta": {
"type": "wagtailimages.Image",
"detail_url": "http://www.lv.local/api/v1/images/504/"
},
"title": "Lighthouse.jpg",
"tags": [],
"width": 1365,
"height": 2048
}
Thanks
As of Wagtail 1.10, you can use ImageRenditionField in your page's api_fields definition to include the URL for an image, rendered at a size of your choosing:
from wagtail.api import APIField
from wagtail.wagtailimages.api.fields import ImageRenditionField
class BarsHomePage(Previewable, Themable, Page):
# ...
api_fields = [
APIField('bars_site_homepage_test'),
APIField('feed_image_fullwidth', serializer=ImageRenditionField('width-800', source='feed_image')),
]

saving image/doc or media files dynamicallly created folder from input form in django

I want to make a Notes Platform where teachers can upload Notes and others can access it . The problem is whenever I am uploading the image it is going to the same folder and I am not able to categorise it according to year,branch,subject, and unit-wise.
Since all the media files are uploaded to the same folder I am unable to get the logic how will i able to fetch the media/image/doc file when a user query for the notes.
my model form is :-
class Note(models.Model):
year_choices = (
( 1 , 'First' ),
( 2 , 'Second'),
( 3 , 'Third' ),
( 4 , 'Fourth')
)
branch_choices = (
( 'IT','IT' ),
( 'EE','EE' ),
( 'CSE','CSE'),
( 'EC','EC' ),
( 'ME','ME' ),
( 'CE','CE' ),
)
unit_choices = ((1,'1'),(2,'2'),(3,'3'),(4,'4'),(5,'5'),
(6,'6'),(7,'7'),(8,'8'),(9,'9'),(10,'10'))
branch = models.CharField(max_length=55,choices=branch_choices)
year = models.IntegerField(choices = year_choices)
subject_name = models.CharField(max_length = 15)
unit = models.IntegerField(choices=unit_choices)
location = 'images'
picture = models.ImageField(upload_to = location)
My notes uploading form and searchform(for searching) is field is :-
class notesform(forms.ModelForm):
class Meta:
model = Note
fields = [ 'year','branch','subject_name','unit','picture' ]
class searchform(forms.ModelForm):
class Meta:
model = Note
fields = [ 'year','branch','subject_name','unit' ]
My notes adding function logic is :-
def addNotes(request):
if request.user.is_authenticated():
if request.method == "POST":
form = notesform(request.POST,request.FILES)
if form.is_valid():
profile = Note()
profile.year = form.cleaned_data["year"]
profile.branch = form.cleaned_data["branch"]
profile.subject_name = form.cleaned_data["subject_name"]
profile.picture = form.cleaned_data["picture"]
post = form.save(commit=False)
post.save()
return redirect('Notes', pk=post.pk)
else:
form = notesform()
return render(request, 'newNotes.html', {'form': form})
else:
return redirect('/accounts/login/')
I want to make upload in such a way that when the teacher fill the form and send upload the media the files upload according to the fields data he will be filling in. For ex:- Teacher fill the form as "First year" then "CSE branch" then "Data Structure" ad "Unit 1" , then it goes to the dynamic media folder "media/images/1/CSE/DATA_STRUCTURE/UNIT_1/".So that i can easily implement the search query .
If it can not applied in this way please suggest some other ways.
The problem is whenever I am uploading the image it is going to the same folder and I am not able to categorise it according to year,branch,subject, and unit-wise.
Instead of creating a new directory for very unit (which is ultimately going to result in a huge directory tree), you can provide a well structured name to uploaded picture and save it in the images directory itself.
Define a function to generate new name for picture:
def generate_picture_name(instance, filename):
url = "images/{0}_{1}_{2}_{3}.jpg".format(
instance.branch, instance.year, instance.subject_name, instance.unit)
return url
And update picture field to use generate_picture_name for saving image file.
picture = models.ImageField(upload_to = generate_picture_name)
Now the image file will be saved as:
media/images/CSE_1_DATA_STRUCTURE_UNIT_1.jpg

Django form validation, raise error on fieldset

I know how to raise an error on a specific field in my django admin form, but I would like the error to be raised on a fieldset. I currently have a list of check boxes in a fieldset and would like an error to be raised, not on a specific field (aka specific check box), but on the entire fieldset.
here is my django admin.py
class EventAdmin(admin.ModelAdmin):
form = EventForm
fieldsets = [
(None, {'fields': [
'approval_state',
'title',
'description'
]
}
),
('Group Owner', {'fields': [
'grpOwner_vcoe',
'grpOwner_cssi',
'grpOwner_essc',
'grpOwner_tmscc',
'grpOwner_inmc',
'grpOwner_cc7',
'grpOwner_ias',
]
}
), ...
class EventForm(forms.ModelForm):
# Form validation
def clean(self):
# Collect data
start = self.cleaned_data.get('start')
end = self.cleaned_data.get('end')
grpOwner_vcoe = self.cleaned_data.get('grpOwner_vcoe')
grpOwner_cssi = self.cleaned_data.get('grpOwner_cssi')
grpOwner_essc = self.cleaned_data.get('grpOwner_essc')
grpOwner_tmscc = self.cleaned_data.get('grpOwner_tmscc')
grpOwner_inmc = self.cleaned_data.get('grpOwner_inmc')
grpOwner_cc7 = self.cleaned_data.get('grpOwner_cc7')
grpOwner_ias = self.cleaned_data.get('grpOwner_ias')
if not (grpOwner_vcoe or grpOwner_cssi or grpOwner_essc or grpOwner_tmscc or grpOwner_inmc or grpOwner_cc7 or grpOwner_ias):
if not self._errors.has_key('Group Owner'):
self._errors['Group Owner'] = ErrorList()
self._errors['Group Owner'].append('Test')
# Check start & end data
if start > end:
if not self._errors.has_key('start'):
self._errors['start'] = ErrorList()
self._errors['start'].append('Event start must occur before event end')
return self.cleaned_data
But this doesn't, I know I can just raise it on each field but I find it much more elegant if I could do it around the fielset
Django forms don't have a concept of fieldsets, they belong to the ModelAdmin class. Therefore there isn't an established way to assign errors to a fieldset instead of a particular field.
You could try overriding the admin templates, in particular, includes/fieldset.html. You could add some code to your form's clean method to make it easy to access the fieldset errors in the template.