Wagtail: Redirect after saving Django model - python

I'm trying to redirect the user to a custom HTML page after saving a BaseSiteSetting model in Wagtail 4.1.1
I'm not sure how to accomplish this, the BaseSiteSetting inherits from Django models.Model which means it's possible to override the save() function but how would I do the actual redirect without having access to the request?
Another acceptable solution would be to add an extra button in the CMS by overriding the default BaseSiteSetting HTML template but I can't seem to get that working either, except for ModelAdmin templates. I've opened a StackOverflow question about that here.
My view of the custom HTML page:
def sync(request):
return render(request, "import.html", {"WS_PROTOCOL": settings.WS_PROTOCOL})
My BaseSiteSetting model:
#register_setting
class AnonymousSuccessStoryImportSetting(BaseSiteSetting):
"""
Setting for importing anonymous success stories.
"""
file = models.FileField(
upload_to="success_story_imports/%Y/%m/%d/",
validators=[validate_file_extension],
help_text="Upload a CSV file, then click 'Save' afterwards",
blank=True,
null=True,
)
date = models.DateTimeField(auto_now_add=True)
class Meta:
verbose_name = "Importer"
I've looked around for possible solutions and found some wagtail hooks but these only apply to the Wagtail Page model, for example, after_publish_page.
It's unfortunate that there's no hook for standard Django models.

It looks to me like BaseSiteSettings is one of the models that has not been converted to use class-based views. So I think you need to monkey patch this edit method to change the 'after save' redirect here: https://github.com/wagtail/wagtail/blob/main/wagtail/contrib/settings/views.py#L121

Related

Wagtail: How to overwrite create/edit template for PageModel

I want to overwrite create.html and edit.html used for models derived from Wagtails 'PageModel'.
If I understand the docs correctly it should be as simple as specifying the attributes:
class MyAdmin(ModelAdmin):
model = MyPage
create_template_name = "myapp/create.html"
edit_template_name = "myapp/edit.html"
My templates are located at projectroot/templates/myapp. It works fine if my model is a Django model but for a PageModel based model the create view still uses wagtailadmin/pages/create.html. I also tried the other location patterns mentioned in the docs w/o success.
Is it possible to change the edit and create templates for a PageModel? Or do the same limitations as for views apply, i.e. only index.html and inspect.html can be overwritten?
ModelAdmin does not provide create, edit, or delete functionality for Page models, as per the documentation note.
NOTE: modeladmin only provides ‘create’, ‘edit’ and ‘delete’ functionality for non page type models (i.e. models that do not extend wagtailcore.models.Page). If your model is a ‘page type’ model, customising any of the following will not have any effect.
It can be a bit confusing as the ModelAdmin system would seem to work for page models also, but there are some other ways to modify how your page can be edited. These will not be scoped to the ModelAdmin area though.
Option 1 - customise the generated form for your MyPage model
If you only want to customise how the edit page form that gets generated you can modify the base_form_class on your page model.
Wagtail has documentation about how to create a custom page form.
Note: WagtailAdminPageForm extends Django's ModelFormMetaClass
Example
from django import forms
from django.db import models
from wagtail.admin.forms import WagtailAdminPageForm
from wagtail.core.models import Page
class EventPageForm(WagtailAdminPageForm):
# ...
class MyPage(Page):
# ...
base_form_class = MyPageForm
Option 2 - customise the view via hooks
To customise the create & edit views for the normal (e.g. clicking edit page on the Wagtail user bar or explorer) page editing interface, you will need to use Wagtail hooks. Here you have access to the request so you will likely be able to determine if you are in the ModelAdmin area.
Create a file called wagtail_hooks.py in your app folder and provide a hook that will return a custom response (this will need to be rendered by your custom view.).
There are separate hooks for before_create_page and before_edit_page
Example from before_create_page docs below.
from wagtail.core import hooks
from .models import AwesomePage
from .admin_views import edit_awesome_page
#hooks.register('before_create_page')
def before_create_page(request, parent_page, page_class):
# Use a custom create view for the AwesomePage model
if page_class == AwesomePage:
return create_awesome_page(request, parent_page)
```python

How to access object count in template using model manager?

I have a model which creates Memo objects. I would like to use a custom Model Manager's posted method to return the total number of Memo objects - then use this number within a template. I am trying to keep as much of my code as possible within my Models and Model Managers and less within my Views as I read that this was a best practice in 'Two Scoops of Django'.
In the shell I can get the number of memos as such:
>>> from memos.models import Memo
>>> Memo.objects.all()
<QuerySet [<Memo: Test Memo 2>, <Memo: Test Memo 1>]>
>>> Memo.objects.all().count()
2
This is what my Model and Model Manager look like:
class MemoManager(models.Manager):
use_for_related_fields = True
def posted(self):
return self.count()
class Memo(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
date_time = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(User, on_delete=models.CASCADE)
objects = MemoManager()
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('memos-detail', kwargs={'pk': self.pk})
I know this is clearly the wrong way to do it but I have confused myself here. So how do I use my Model Manager to get the count of objects and use it in a template like: {{ objects.all.count }}?
P.S. I see other posts that show how to do this within the view but as stated I am trying not to use the view. Is using the view required? I also understand my posted method is written incorrectly.
I'm sorry but you have misinterpreted what was written in TSD. The Lean View Fat Model is meant to keep code which pertains to 'business logic' out of the views, and certain model specific things. A request should be handled by a view. So when you want to load a template, you must first have a GET request to your app.
A view function should be written such that Validation of POST data or the Creation of a new object in DB or Querying/Filtering for GET requests should be handled in the corresponding serializer/model/model manager.
What should be happening while you want to load your template.
Have a url for the template that you have created and a view function mapped for it
In the view function you should render said template and pass the necessary data inside the context.
To keep in line with the Lean View Fat Model style, if you want to get a Queryset of of Memo's but only those which have their is_deleted fields set to False, you can overwrite the model manager get_queryset() method for Memo model.
If you want to create a new Memo with a POST request, you can handle
the creation using a ModelForm!
Hope this clears things up!
EDIT:
How to pass a context to a template, in your case the memo count.
def random_memo_view(request):
context = {'memo_count': Memo.posted()}
return render(request, 'template.html', context=context)
RE-EDIT
I just checked that you were using DetailView. In this case follow this from the django docs.
Class Based Views: Adding Extra Context

How to pass values to defined template from views.py in Django-python

I have a model like this:
class EventTypeCategory(models.Model):
name = models.CharField(max_length=50, verbose_name="Name")
user = models.ForeignKey(User, verbose_name="User")
Message_slug = models.SlugField(blank=True, verbose_name="Message")
def __unicode__(self):
return self.name
In urls.py:
url(r'^categ/$',
'eventcateg_detail', name='eventcateg_detail'),
In views.py:
def eventcateg_detail(request,event_categ_id=None, event_categ_slug=None):
I want to add/edit/delete(CRUD) above defined value i.e name and Message_slug by template level. I am not getting any hint how to relate url.py with views.py and what should be definition of eventcateg_detail function.How this function will pass values to template (template name will be categ.html)
I am newbie in Django :)
want your help
You need to allow the URL to accept parameters to allow you specify which event category you want to view:
/categ/outdoor-events/
/categ/catered-events/
...
Do do this, you use a named URL pattern in your url scheme:
url(r'^categ/(?P<slug>[-\w]+)/$','eventcateg_detail', name='eventcateg_detail'),
and in your view:
from django.shortcuts import get_object_or_404, render
def eventcateg_detail(request,slug):
return render(request, "categ.html", {
'obj' : get_object_or_404(EventCateg, Message_slug =slug) # You should change Message_slug to just slug
})
and in your template:
<h1>{{ obj.name }}</h1>
So when a user enters a URL like we have outlined above, it gets matched to our URL pattern and the slug part of the url (catered-events) gets passed as a parameter to our view.
It's better that you follow the Django tutorial first, this is all covered in there. See for example part 3 of the tutorial for more information on how to relate urls.py with views.py and part 4 discusses passing variables to the template.
I believe that a view function is only passed an httprequest when it is called by the Django framework, the other two parameters of the function will only be useful if you call the function yourself but will not be useful through the web.
As pointed out in the comments I was mistaken in my belief, extra parameters can be passed as dynamic urls (i.e. urls designated like this url(r'^polls/(?P<poll_id>\d+)/$', 'polls.views.detail'),. See this link and the answer by #pastylegs
The Django Admin will allow you to edit all model fields if this is what you are after. Instructions on setting it up can be found in the Django documentation.
However I think what you are asking is how to enable CRUD editing through the web to users who are not admin level users. In that case you have many options. One of those options is to use a pre-built framework for Django like piston. Another way would be to use generic views
The other option is to build views yourself enabling operations on your model. In that case all of Django is available to you. You can pass parameters to your custom functions within the httprequest, for example as POST data.

Ajax form handling in django admin

I have two models:
class Customer(models.Model):
(...)
class CustomerMemo(models.Model):
(...)
customer = models.ForeignKey(Customer)
text = models.TextField()
And in my admin.py
class MemoInline(admin.StackedInline):
model = CustomerMemo
class Customer(admin.ModelAdmin):
(...)
inlines = (MemoInline,)
I want to make autosave for these inline fields.
I think there should be ajax request every 30 seconds.
But now there two questons:
How to make ajax request which gets requred data from admin page?
How would be better to add admin custom view which handle this ajax request?
I've read about dajax, but I can't get how it could help me with my task.
Thanks
Redefine admin template and add a JS with some function which will gather form data with $(form).serialize() and make an ajax POST to the server. URL for POST can be admin page itself (if you don't mind overwriting the object) or you can write your own view with necesssary form and formsets. Maybe you'll also need to add value of the "Save" button to POST load.

Django, class-views: How can I save session data with a form's object?

I'm trying to store the username from the current request's session into a db object. How can I do this from within a class-based view? Is there a "clean" way to do this? What should I override/subclass?
I have a model that looks like this:
from django.contrib.auth.models import User
class Entry(django.db.models.Model):
...
author = models.ForeignKey(User, editable=False)
I also have a view based on the built-in generic view django.views.generic.CreateView. I'm also using the default ModelForm class that goes with my model, and the default {{ form }} in my template. AFAIK, the session and authentication apps/middleware are set up properly---as per default in new Django projects.
I found this post, which is getting at about the same thing, but from the wrong angle, and using function views instead.
My thinking so far was to override something in the form class and insert the username into the cleaned data. Is there a better way? Is there a right way?
Edit: Solution so far, non-working, with an IntegrityError: author_id cannot be null
from django.views.generic import CreateView
class Index(CreateView):
model = magicModel
template_name = "index.html"
success_url = "/magicWorked"
...
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.author = request.user
return super(Index, self).form_valid(form)
I wrote this based on what I found in django/views/generic/edit.py, which uses this implementation for class ModelFormMixin:
def form_valid(self, form):
self.object = form.save()
return super(ModelFormMixin, self).form_valid(form)
This is the method called by super().form_valid() above.
Edit: The problem with my solution was my understanding of Python's inheritance model. When the super-class calls form_valid(), it calls its own version, not my override; my code was never running at all.
The "correct" way to do this is to write your own view for object creation if the generic view doesn't suffice. Creation views are relatively short and there are numerous examples of how to save foreign keys.
Incidentally, Django's 1.3 docs say somewhere in there that modifications to the authentication model used by the admin app are being "discussed," such as adding per-instance permissions. (The current auth model supports only per model permissions.) The dev's might also add an implementation for what I'm trying to achieve. After all, user-associated data is used by nearly all websites.

Categories

Resources