Custom wagtail page model save method called twice - django

In a Django+Wagtail project, I have a custom method which I call from the model save() method. It works - but everytime I save an instance via the wagtail admin interface, this method is called twice - why?
# models.py
from wagtail.core.models import Page
class ArticlePage(Page):
def my_method(self):
print('I will be printed twice on save...')
def save(self, *args, **kwargs):
self.my_method()
super().save(*args, **kwargs)
By using traceback.print_stack as suggested by HÃ¥ken Lid I've found out that the method is first called via
File ".../.venv/lib/python3.6/site-packages/wagtail/admin/views/pages.py", line 336, in edit
submitted_for_moderation=is_submitting,
File ".../.venv/lib/python3.6/site-packages/wagtail/core/models.py", line 653, in save_revision
self.save(update_fields=update_fields)
and the second time via
File ".../.venv/lib/python3.6/site-packages/wagtail/admin/views/pages.py", line 343, in edit
revision.publish()
File ".../.venv/lib/python3.6/site-packages/wagtail/core/models.py", line 1498, in publish
page.save()
But even with this information I'm not aware how to only trigger my method on the second save...
Env:
Django 2.0.4
Wagtail 2.0.1

In case you want to do something on page publish, You can use the page_published signals as below:
from django.dispatch import receiver
from wagtail.core.signals import page_published
#receiver(page_published)
def do_stuff_on_page_published(instance, **kwargs):
print('=======================',instance,kwargs)
In case of save, you can also check the update_fields argument in the kwargs:
def save(self, *args, **kwargs):
if kwargs.get('update_fields'):
pass # save not called from publish
# do_stuff_on_save()
else:
pass
# do_stuff_on_publish()
return super().save(*args, **kwargs)
For more info about wagtail signal code visit this link
and this for the official docs
http://docs.wagtail.io/en/v1.7/reference/signals.html

Related

Can you trigger a signal upon partial update of django object via the django rest framework?

I started noticing that the patch method in django rest framework doesn't actually trigger signals, post methods seem to work fine. This is what I have:
#receiver(signals.pre_save, sender=Example)
def populate_time_fields_based_on_state(sender, **kwargs):
example = kwargs.get('instance')
if example.start_datetime is None and example.projected_end_datetime is None and example.state.label == 'Assigned':
example.start_datetime = datetime.datetime.now()
example.projected_end_datetime = example.created_datetime + datetime.timedelta(
days=example.som_field)
example.save()
And I'm testing this via:
client = APIClient()
client.patch(f'/api/example/1/', {'state': 'Assigned'})
Is there a way to tell it to trigger the signal? Do I need to override the update method in my serializer? I'm trying this:
def partial_update(self, request, *args, **kwargs):
response = super().partial_update(request, *args, **kwargs)
instance = self.get_object()
instance.save()
return response
But it's quite hacky
In your app directory there should be an apps.py, see the docs for the format of it.
Generally it ends up looking like the following, as opposed to the example where they wire up the signal manually. Note that I have a "project/apps/" structure here, but just change the module name depending on where the files actually live:
#project/apps/my_app/__init__.py
default_app_config = 'project.apps.my_app.apps.MyAppConfig'
#project/apps/my_app/apps.py
from django.apps import AppConfig
class MyAppConfig(AppConfig):
name = "project.apps.my_app"
verbose_name = "MyApp"
def ready(self):
from project.apps.my_app import signals
# ... other init &/or logging
Note: Feel free to delete the line in init.py, and play with the name in the app config. I'm not sure how critical they actually are

Django rest framework- calling another class-based view

I have pored over several similar posts (and Calling a class-based view of an app from another app in same project seemed promising, but does not work), but some are older and none quite work for me. Here's my setup (using Django==2.0.6, djangorestframework==3.8.2)
I have a basic model (simplified here):
from django.db import models
class Resource(models.Model):
name = models.CharField(max_length=100, null=False)
I have a basic endpoint where I can list and create Resource instances:
from rest_framework import generics, permissions
from myapp.models import Resource
from myapp.serializers import ResourceSerializer
class ListAndCreateResource(generics.ListCreateAPIView):
queryset = Resource.objects.all()
serializer_class = ResourceSerializer
permission_classes = (permissions.IsAuthenticated,)
(afaik, the details of the serializer are not relevant, so that is left out).
Anyway, in addition to that basic endpoint, I have another API endpoint which performs some actions, but also creates some Resource objects in the process. Of course, I would like to make use of the functionality encapsulated in the ListAndCreateResource class so I only have to maintain one place where Resources are created.
I have tried:
Attempt 1:
class SomeOtherView(generics.CreateAPIView):
def post(self, request, *args, **kwargs):
# ... some other functionality...
# ...
response = ListAndCreateResource().post(request, *args, **kwargs)
# ... more functionality...
return Response({'message': 'ok'})
Unfortunately, that does not work for me. In my trace, I get:
File "/home/projects/venv/lib/python3.5/site-packages/rest_framework/generics.py", line 111, in get_serializer
kwargs['context'] = self.get_serializer_context()
File "/home/projects/venv/lib/python3.5/site-packages/rest_framework/generics.py", line 137, in get_serializer_context
'request': self.request,
AttributeError: 'ListAndCreateResource' object has no attribute 'request'
Attempt 2:
This attempt tries to use the as_view method which is part of all Django class-based views:
class SomeOtherView(generics.CreateAPIView):
def post(self, request, *args, **kwargs):
# ... some other functionality...
# ...
response = ListAndCreateResource.as_view()(request, *args, **kwargs)
# ... more functionality...
return Response({'message': 'ok'})
But that gives up with:
AssertionError: The `request` argument must be an instance of `django.http.HttpRequest`, not `rest_framework.request.Request`
So my question is...is there a straightforward way to do this? I can access the _request attribute of the rest_framework.request.Request object (which is of type django.http.HttpRequest, but then I do not have any of the authentication details that are contained in the DRF Request object (indeed, my ListAndCreateResource returns a 403 if I use response = ListAndCreateResource().as_view()(request._request, *args, **kwargs) in attempt #2 above).
Thanks in advance!
This seems a bit late, but in case anyone is wondering.
class SomeOtherView(generics.CreateAPIView):
def post(self, request, *args, **kwargs):
# ... some other functionality...
# ...
response = ListAndCreateResource.as_view()(request, *args, **kwargs)
# ... more functionality...
return Response({'message': 'ok'})
The as_view() is a function that when called, returns a function that takes a request, *args, **kwargs. So basically, a class view is an encapsulated function view.
I think you can use request._request. The DRF keeps a protected member _request, as is, received from the API call.
You can access the request with self.request in class based views.

Execute delete() within save() in Django

I'm working on a Django/Wagtail project. I'm trying to build a very customized feature that requires an object to be deleted when hitting the Save button when certain conditions are met.
I override the Save method:
def save(self, *args, **kwargs):
if condition:
return super(ArticleTag, self).delete()
else:
return super(ArticleTag, self).save(*args, **kwargs)
I know this looks very odd and completely anti-adviseable, but it is exactly the behavior I'm trying to achieve.
Is there a better or "correct" way to do this?
Are there other steps to exactly reproduce the behavior as if the user had hit Delete directly?
If the object already exists in your db, you can do as follows:
def save(self, *args, **kwargs):
if condition:
self.delete() # you do not need neither to return the deleted object nor to call the super method.
else:
return super(ArticleTag, self).save(*args, **kwargs)
Using signals receivers
signals.py
from django.dispatch import receiver
from django.db.models.signals import post_save
__all__ = ['check_delete_condition']
#receiver(post_save, sender="yourapp.yourmodel")
def check_delete_condition(instance, raw, created, using, updatefields, **kwargs):
if condition:
instance.delete()
in your apps.py you can't put the signals import
from .signals import *
#rest of code

Is there a way to call a function in ListView?

I have a ListView and I want to execute a script in python to update our database everytime I enter in the listview, or periodically.
Is there a way to do that ?
class AccessPointList(generic.ListView): #receives the whole set of AccessPoint and send to HTML
model = AccessPoint
#call a function in python -> c.update()
#or create a def to update our database
Sure, just override one of the methods when you want to run it, then call super to continue with normal execution of the ListView
class AccessPointList(generic.ListView):
model = AccessPoint
def get(self, request, *args, **kwargs):
# call your function
return super().get(request, *args, **kwargs)

How to install a custom upload handler in a Django generic view?

In order to provide progress feedback of file uploads I need to install a custom upload handler for a specific view. This is documented for "classical" Django views at
https://docs.djangoproject.com/en/dev/topics/http/file-uploads/#modifying-upload-handlers-on-the-fly
For generic views, however, I could not find any instructions and I came up with the following:
from django.utils import importlib
from django.core.exceptions import ImproperlyConfigured
from django.views.decorators.csrf import csrf_protect
class UploadHandlerMixin(object):
'''
A mixin for Django generic views that installs a custom upload handler in front of
the current chain of upload handlers.
You specify the handler to install by overriding the 'upload_handler' attribute of
the class, specifying the module and class name in the form 'path.to.module.class':
class MyView(UploadHandlerMixin, View):
upload_handler = 'path.to.module.MyUploadHandler'
If you do not override 'upload_handler', no additional upload handler will be
installed.
If the CsrfViewMiddleware is installed (which is the default) then you must use
your view as follows in your urls.py:
from django.views.decorators.csrf import csrf_exempt
url(r'^.../$', csrf_exempt(MyView.as_view()), ...),
Internally, the UploadHandlerMixin mixin will install the upload handler and then
perform the CSRF check. (This is necessary because the CSRF check inspects
request.POST, and afterwards upload handlers cannot be changed, see documentation
link given below.)
The handler is installed as described in the Django documentation "Modifying upload handlers
on the fly", see https://docs.djangoproject.com/en/dev/topics/http/file-uploads/#modifying-upload-handlers-on-the-fly
'''
upload_handler = None
def dispatch(self, request, *args, **kwargs):
if not self.upload_handler is None:
request.upload_handlers.insert(0, UploadHandlerMixin._instantiate_upload_handler(self.upload_handler, request))
return _uploadhandler_dispatch(request, self, *args, **kwargs)
#staticmethod
def _instantiate_upload_handler(path, *args, **kwargs):
i = path.rfind('.')
module, attr = path[:i], path[i+1:]
try:
mod = importlib.import_module(module)
except ImportError, e:
raise ImproperlyConfigured('Error importing upload handler module %s: "%s"' % (module, e))
except ValueError, e:
raise ImproperlyConfigured('Error importing upload handler module. Is FILE_UPLOAD_HANDLERS a correctly defined list or tuple?')
try:
cls = getattr(mod, attr)
except AttributeError:
raise ImproperlyConfigured('Module "%s" does not define a "%s" upload handler backend' % (module, attr))
return cls(*args, **kwargs)
#csrf_protect
def _uploadhandler_dispatch(request, view, *args, **kwargs):
return super(UploadHandlerMixin, view).dispatch(request, *args, **kwargs)
Is this the "recommended way" to accomplish the task? Is it okay security-wise?
Answering myself, an alternative to a service-side solution (like Django with a custom upload handler) is a client-side solution like JQuery File Upload. It uses the fact that more recent browsers allow the upload progress to be read on the client. In my case, this was much simpler to implement and causes no additional server load.