Adding a common validation to all text fields on all serializers - django

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

Related

How to use username as a string in model in django?

I want to use the username of the account in which my django is running as a string to load the model fields specific to that username. I have created a file 'survey.py' which returns a dictionary and I want the keys as the fields.
How can I get the username as string?
from django.db import models
from django.contrib.auth.models import User
from multiselectfield import MultiSelectField
from survey_a0_duplicate import details, analysis
import ast
class HomeForm1(models.Model):
user= models.OneToOneField(User, on_delete=models.CASCADE,)
details.loadData(survey_name = user)#<=====This loads the data for specific user<======
global f1
f1=analysis.getQuestion(in_json=False)#<====We get the dictionary here<========
d=list(f1.keys())
###################assign the filters#######################################################
for k in d:
q=list(f1[k].keys())
q.sort()
choices=tuple(map(lambda f: (f,f),q))
locals()[k]=MultiSelectField(max_length=1000,choices=choices,blank=True)
def save(self, *args, **kwargs):
if self.pk is None:
self.user= self.user.username
super(HomeForm1,self).save(*args,**kwargs)
def __str__(self):
return self.title
This is not how you write Django code. Global variables are a bad idea anyway, but you must not use them in a multi-user, multi-process environment like Django. You will immediately have thread-safety issues; you must not do it.
Not only is there an explicit global in the code you have shown, there is clearly one inside survey_a0_duplicate - since details.loadData() does not actually return anything but you then "get the dictionary" from analysis.getQuestion. You must remove the globals from both locations.
Also, your save method is totally wrong. You have the user relationship; why would you overwrite it with the username? That not only makes no sense, it specifically destroys the type of the field that you have set. Just don't do it. Remove the entire save method.
But you need to stop messing about with choices at class level. That is never going to work. If you need to dynamically set choices, do in in a form, where you can customise the __init__ method to accept the current user and build up the choices based on that.

Log all save/update/delete actions in all django models

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.

Django documentation Part 2: why can't I import Poll, PollAdmin and Choices in a single line?

Working on the Django tutorial "Writing your own Django app," and I'm on Part 2.
Midway through, it instructs me to add a line to the admin so that the admin will recognize not just Poll (and PollAdmin, which the tutorial has been configuring for some custom poll-presenting options), but also Choice. Here's the (short) updated admin.py:
from polls.models import Poll
from polls.models import Choice
from django.contrib import admin
class PollAdmin(admin.ModelAdmin):
fieldsets = [
(None, {'fields': ['question']}),
('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}),
]
admin.site.register(Poll, PollAdmin)
admin.site.register(Choice)
Looking at this, I can't figure out why I can't simply write
admin.site.register(Poll, PollAdmin, Choice)
except that this gives me a TypeError, because
register() takes at most 3 arguments (4 given)
This seems really... arbitrary to me. I don't understand why register only takes at most 3 arguments. My understanding of Django is still at a very voodoo, cargo-cult level, so I get that this Just. Doesn't. Work., but I was wondering if some light could be shed on why I can't pull all three elements from admin.site at the same time.
Because you've got two separate types of registration going on here, with two separate types of objects. I think you're thinking that this is saying "register these three things with the admin", but that's not quite what's going on.
The first line is saying "register the Poll model with the admin, using the PollAdmin class I've defined."
The second line is saying "register the Choice model with the admin, using the default settings."
So it wouldn't make sense to have them all in the same line. You're only registering one model at a time, but one uses some explicit options, and the other uses the default options.
register accepts only one or two arguments (the third is self which passed automatically by the class). If you specify one argument, it must be a model class, and the Django admin will simply use the default ModelAdmin for it. If you specify two arguments, the first must be a model class, and the second must be a ModelAdmin subclass.
You can't just arbitrarily keep passing arguments to methods, each argument means something.
try this:
from django.contrib import admin.site
register(Poll, PollAdmin, Choice)
Suppose, you have a method within a class "myclass" , such as :
def example_method(param1,param2):
//do stuff
When you use myclass.example_method(param2), you have param1 being implicitly passed to myclass.
Look at the source code. Is not a Django stuff, but is basic Python.
The register method:
def register(self, model_or_iterable, admin_class=None, **options):
"""
stuff to read
"""
#...code...
validate(admin_class, model)
self._registry[model] = admin_class(model, self)
initiates a ModelAdmin object ...
class ModelAdmin(BaseModelAdmin):
def __init__(self, model, admin_site):
self.model = model
self.opts = model._meta
self.admin_site = admin_site
super(ModelAdmin, self).__init__()
... if you read the use of "option", is there to solve a bug and if someone will ever want to extend ModelAdmin! Like so:
class MyModelAdmin(ModelAdmin):
def __init__(self, model, admin_site, my_param = None):
self.my_property = my_param
super(MyModelAdmin, self).__init__(model, admin_site)
If you just started learning Django and Python this kind of questions are totally useless and non-productive!

Adding to the "constructor" of a django model

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.

Loose coupling of apps & model inheritance

I have a design question concerning Django. I am not quite sure how to apply the principle of loose coupling of apps to this specific problem:
I have an order-app that manages orders (in an online shop). Within this order-app I have two classes:
class Order(models.Model):
# some fields
def order_payment_complete(self):
# do something when payment complete, ie. ship products
pass
class Payment(models.Model):
order = models.ForeignKey(Order)
# some more fields
def save(self):
# determine if payment has been updated to status 'PAID'
if is_paid:
self.order.order_payment_complete()
super(Payment, self).save()
Now the actual problem: I have a more specialized app that kind of extends this order. So it adds some more fields to it, etc. Example:
class SpecializedOrder(Order):
# some more fields
def order_payment_complete(self):
# here we do some specific stuff
pass
Now of course the intended behaviour would be as follows: I create a SpecializedOrder, the payment for this order is placed and the order_payment_complete() method of the SpecializedOrder is called. However, since Payment is linked to Order, not SpecializedOrder, the order_payment_complete() method of the base Order is called.
I don't really know the best way to implement such a design. Maybe I am completely off - but I wanted to build this order-app so that I can use it for multiple purposes and wanted to keep it as generic as possible.
It would be great if someone could help me out here!
Thanks,
Nino
I think what you're looking for is the GenericForeignKey from the ContentTypes framework, which is shipped with Django in the contrib package. It handles recording the type and id of the subclass instance, and provides a seamless way to access the subclasses as a foreign key property on the model.
In your case, it would look something like this:
from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic
class Payment(models.Model):
order_content_type = models.ForeignKey(ContentType)
order_object_id = models.PositiveIntegerField()
order = generic.GenericForeignKey('order_content_type', 'order_object_id')
You don't need to do anything special in order to use this foreign key... the generics handle setting and saving the order_content_type and order_object_id fields transparently:
s = SpecializedOrder()
p = Payment()
p.order = s
p.save()
Now, when your Payment save method runs:
if is_paid:
self.order.order_payment_complete() # self.order will be SpecializedOrder
The thing you want is called dynamic polymorphism and Django is really bad at it. (I can feel your pain)
The simplest solution I've seen so far is something like this:
1) Create a base class for all your models that need this kind of feature. Something like this: (code blatantly stolen from here)
class RelatedBase(models.Model):
childclassname = models.CharField(max_length=20, editable=False)
def save(self, *args, **kwargs):
if not self.childclassname:
self.childclassname = self.__class__.__name__.lower()
super(RelatedBase, self).save(*args, **kwargs)
#property
def rel_obj(self):
return getattr(self, self.childclassname)
class Meta:
abstract = True
2) Inherit your order from this class.
3) Whenever you need an Order object, use its rel_obj attribute, which will return you the underlying object.
This solution is far from being elegant, but I've yet to find a better one...