There are several models in my django app. Some of them derive from models.Model, some - from django-hvad's translatable model.
I want to log every save/delete/update operation on them. I am aware of standard django logger of admin actions, but they are too brief and non-verbose to satisfy my needs.
Generally speaking, one common way to achieve this is to define super-class with these operations and extend each model from it. This is not my case because some of my models are translatable and some are not.
Second way are aspects/decorators. I guess, python/django must have something like that, but I don't know what exactly :)
Please, provide me with the most suitable way to do this logging.
Thanks!
You could write a mixin for your model.
import logging
class LogOnUpdateDeleteMixin(models.Model):
pass
def delete(self, *args, **kwargs):
super(LogOnUpdateDeleteMixin, self).delete(*args, **kwargs)
logging.info("%s instance %s (pk %s) deleted" % (str(self._meta), str(self), str(self.pk),) # or whatever you like
def save(self, *args, **kwargs):
super(LogOnUpdateDeleteMixin, self).save(*args, **kwargs)
logging.info("%s instance %s (pk %s) updated" % (str(self._meta), str(self), str(self.pk),) # or whatever you like
class Meta:
abstract = True
Now just use it in your model.
class MyModel(LogOnUpdateDeleteMixin, models.Model):
...
# Update/Delete actions will write to log. Re-use your mixin as needed in as many models as needed.
You can re-use this mixin again and again. Perform translation as you wish, set some attributes in your models and check for them in the mixin.
Related
I am looking for a way (or several ways if needed) to add a common/shared validation function to all text fields in an DRF API. I hope to be able to do this in the least intrusive way possible, since there are already so many serializers throughout the API.
This is a horrible thing, and wrong, but its a requirement. Saying "don't do that" or "you shouldn't do this" is not helpful. I know. Its not up to me.
Given a serializer like this:
class MySerializer(ModelSerializer):
description = CharField()
class Meta:
model = SomeModel
fields = ["name", "description"]
... both of these would somehow run a validation function. For example, in the base CharField the framework adds two validators, and essentially I'd like to add a third.
class CharField(Field): # site-packages/rest_framework/fields.py
def __init__(self):
..
self.validators.append(ProhibitNullCharactersValidator())
self.validators.append(ProhibitSurrogateCharactersValidator())
Is there some clever way to do this? I don't want to resort to literally hacking the source code, or replacing CharField throughout the application.
The solution I ended up going with is below. It loads at django startup in my settings module which has a nice z_patches.py where other things like this live (replacing the default filter classes, etc)
def wrap_init(old_init):
#functools.wraps(old_init)
def __new_init__(self, **kwargs):
old_init(self, **kwargs)
self.validators.append(MyCustomValidator())
return __new_init__
CharField.__init__ = wrap_init(CharField.__init__)
If you absolutely know the risks, then you could do something like this in one of your apps.py:
from django.apps import AppConfig
from rest_framework.fields import Field
from rest_framework.serializers import CharField
def _init(self, **kwargs):
...
Field.__init__(self, **kwargs)
...
self.validators.append(YourCustomValidator())
class MyAppConfig(AppConfig):
...
def ready(self):
CharField.__init__ = _init
Which option is best, 1 or 2?
1.
class TopicForm(forms.Form):
name = forms.CharField(required=True)
body = RichTextFormField(required=True)
def save(self, request):
t = models.Topic(user=request.user,
site=get_current_site(request),
name=self.cleaned_data['name'],
body=self.cleaned_data['body'])
t.slug = slugify(self.name)
t.body_html = seo.nofollow(seo.noindex(self.body))
t.ip = utils.get_client_ip(request)
t.save()
or 2.
class Topic(models.Model):
...
def save(self, *args, **kwargs):
self.slug = slugify(self.name)
self.body_html = seo.nofollow(seo.noindex(self.body))
self.ip = utils.get_client_ip(request)
super(Topic, self).save(*args, **kwargs)
The difference is that the first version is only applied when modifying objects through the form, while the second is applied whenever the model is saved (though that is still a subset of all the ways in which database rows can be modified in Django). Even if you currently only create objects through forms, I think it's still a useful distinction to keep in mind.
It looks to me like a mixture of the two makes sense in your case. A slug is something that you will always want to set based on name - that is, it's inherent to the model itself. On the other hand, the idea of a client_ip seems inexorably tied to the notion of creating an object with a form via a web request.
Of course, you are in a better position to know about the specifics of this model, but that is the general way I would approach the question.
It depends. If this should be applied to every models, then it is better in the model. It will assure you that every Topic object will have correct values, even those you are edited from the admin interface.
The form should be use only to check data from the user and the model is appropriate to automatize this kind of task (generate data before saving the object). Be careful, this shouldn't raise Exception or invalidate data however.
Personally I would prefer the second option. The model should define the business logic too, while forms should just handle user I/O. This way your application will keep consistent even if used in a programmatic way (imported and called from other code).
You shouldnt use 2. its better to use a signal like pre-save or post-save
Source: https://docs.djangoproject.com/en/dev/topics/signals/
#receiver(pre_save, sender=Topic)
def topic_pre_save_handler(sender, instance, **kwargs):
instance.slug = slugify(self.name)
instance.body_html = seo.nofollow(seo.noindex(self.body))
instance.ip = utils.get_client_ip(request)
I have some page elements that don't change often and are displayed on every page like some adbars, footer content and such.
I want to change settings for this elements in my admin interface, so I have models for them.
Is there a best practice in django to deal with these elements?
Not really, no. You're describing a singleton pattern, so you might want to implement a singleton model type:
class SingletonModel(models.Model):
class Meta:
abstract = True
def save(self, *args, **kwargs):
self.id = 1
super(SingletonModel, self).save(*args, **kwargs)
def delete(self, *args, **kwargs):
pass
That will ensure that any model that inherits from that class can only ever have one member and that it can't be deleted. Other than that, I would suggest combining everything into just one model called something like SiteSettings with fields for header, footer, etc, instead of separate model for each.
You could use a context processor to add them to the context and use a simple caching mechanism so you don't have to hit the db every time like, http://eflorenzano.com/blog/2008/11/28/drop-dead-simple-django-caching/
Hard to answer - what exactly are you asking?
You can display these models in your base template. You can use caching to cut down on database calls.
I want to do an extra initalization whenever instances of a specific django model are created. I know that overriding __init__ can lead to trouble. What other alternatives should I consider?
Update. Additional details: The intent is to initialize a state-machine that the instances of that model represent. This state-machine is provided by an imported library, and it's inner state is persisted by my django-model. The idea is that whenever the model is loaded, the state machine would be automatically initialized with the model's data.
Overriding __init__ might work, but it's bad idea and it's not the Django way.
The proper way of doing it in Django is using signals.
The ones that are of interest to you in this case are pre_init and post_init.
django.db.models.signals.pre_init
Whenever you instantiate a Django
model, this signal is sent at the beginning of the model’s __init__()
method.
django.db.models.signals.post_init
Like pre_init, but this one is sent
when the __init__(): method finishes
So your code should be something like
from django.db import models
from django.db.models.signals import post_init
class MyModel(models.Model):
# normal model definition...
def extraInitForMyModel(**kwargs):
instance = kwargs.get('instance')
do_whatever_you_need_with(instance)
post_init.connect(extraInitForMyModel, MyModel)
You can as well connect signals to Django's predefined models.
While I agree that there often is a better approach than overriding the __init__ for what you want to do, it is possible and there might be cases where it could be useful.
Here is an example on how to correctly override the __init__ method of a model without interfering with Django's internal logic:
from django.db import models
class Book(models.Model):
title = models.CharField(max_length=100)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# add your own logic
The two suggested methods in the docs rely on the instance being created in an arbitrary way:
Add a classmethod on the model class:
from django.db import models
class Book(models.Model):
title = models.CharField(max_length=100)
#classmethod
def create(cls, title):
book = cls(title=title)
# do something with the book
return book
book = Book.create("Pride and Prejudice")
Add a method on a custom manager:
class BookManager(models.Manager):
def create_book(self, title):
book = self.create(title=title)
# do something with the book
return book
class Book(models.Model):
title = models.CharField(max_length=100)
objects = BookManager()
book = Book.objects.create_book("Pride and Prejudice")
If that is your case, I would go that way. If not, I would stick to #vartec's answer.
Suppose I want to create and update a model. What fields are displayed and the type of validation depends on the action (create or update). But they still share a lot of the same validation and functality. Is there a clean way to have a ModelForm handle this (besides just if instance exists everywhere) or should I just create two different model forms?
Two possibilities spring to mind. You could set an attribute in the form's __init__ method, either based on a parameter you explicitly pass in, or based on whether self.instance exists and has a non-None pk:
class MyModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
# either:
self.edit = kwargs.pop('edit', False)
# or:
self.edit = hasattr(self, instance) and self.instance.pk is not None
super(MyModelForm, self).__init__(*args, **kwargs)
# now modify self.fields dependent on the value of self.edit
The other option is to subclass your modelform - keep the joint functionality in the base class, then the specific create or update functionality in the subclasses.