django model - on_delete=models.PROTECT() - django

I'm trying to use on_delete with my models but my IDE is asking me for: collector, fields, sub_objs, using (i.e. ..., on_delete=models.PROTECT(collector, fields, sub_objs, using)).
Can someone please tell me what these are and give me a quick example because I can find them documented anywhere :(

Ignore your IDE. It is trying to get you to call the models.PROTECT function, which does indeed take those arguments. But you actually want to pass the function itself:
my_field = models.ForeignKey(..., on_delete=models.PROTECT)
ie without the parentheses that would call the function.
(Insert rant about using an IDE with a dynamic language here...)

Import like:(Python 2.7)
from django.db.models.deletion import PROTECT
Then you can use it directly.
category = ForeignKey(TCategory, PROTECT, null=False, blank=False)

models.PROTECT prevents deletions but does not raise an error by default.
you can create a custom exception for it, which is already protected.
from django.db import IntegrityError
class ModelIsProtectedError(IntegrityError):
pass
def prevent_deletions(sender, instance, *args, **kwargs):
raise ModelIsProtectedError("This model can not be deleted")
#in your models.py:
pre_delete.connect(prevent_deletions, sender=<your model>)

Related

Adding a common validation to all text fields on all serializers

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

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.

Django delete foreign object?

If we set up a profile how Django recommends:
class Profile(models.Model):
user = models.ForeignKey(User, unique=True)
Then when you delete the User object from Django admin, it deletes his profile too.This is because the profile has a foreign key to user and it wants to protect referential integrity. However, I want this functionality even if the pointer is going the other way. For example, on my Profile class I have:
shipper = models.ForeignKey(Shipper, unique=True, blank=True, null=True)
carrier = models.ForeignKey(Carrier, unique=True, blank=True, null=True)
affiliat = models.ForeignKey(Affiliate, unique=True, blank=True, null=True, verbose_name='Affiliate')
And I want it so that if you delete the Profile it'll delete the associated shipper/carrier/affiliate objects (don't ask me why Django made "affiliate" some weird keyword). Because shippers, carriers and affiliates are types of users, and it doesn't make sense for them to exist without the rest of the data (no one would be able to log in as one).
The reason I didn't put the keys on the other objects, is because then Django would have to internally join all those tables every time I wanted to check which type the user was...
While using a post_delete signal as described by bernardo above is an ok approach, that will work well, I try to avoid using signals as little as humanly possible as I feel like it convolutes your code unnecessarily by adding behavior to standard functionality in places that one might be expecting.
I prefer the overriding method above, however, the example given by Felix does have one fatal flaw; the delete() function it is overriding looks like this:
def delete(self, using=None):
using = using or router.db_for_write(self.__class__, instance=self)
assert self._get_pk_val() is not None, "%s object can't be deleted because its %s attribute is set to None." % (self._meta.object_name, self._meta.pk.attname)
collector = Collector(using=using)
collector.collect([self])
collector.delete()
Notice the parameter 'using', in most cases we call delete() with empty arguments so we may have even known it was there. In the above example this parameter is buried by us overriding and not looking at the superclass functionality, if someone where to pass the 'using' parameter when deleting Profile it will cause unexpected behavior. To avoid that, we would make sure to preserve the argument along with its default lika so:
class Profile(models.Model):
# ...
def delete(self, using=None):
if self.shipper:
self.shipper.delete()
if self.carrier:
self.carrier.delete()
if self.affiliat:
self.affiliat.delete()
super(Profile, self).delete(using)
One pitfall to the overriding approach, however, is that delete() does not get explicitly called per db record on bulk deletes, this means that if you are going to want to delete multiple Profiles at one time and keep the overriding behavior (calling .delete() on a django queryset for example) you will need to either leverage the delete signal (as described by bernardo) or you will need to iterate through each record deleting them individually (expensive and ugly).
A better way to do this and that works with object's delete method and queryset's delete method is using the post_delete signal, as you can see in the documentation.
In your case, your code would be quite similar to this:
from django.db import models
from django.dispatch import receiver
#receiver(models.signals.post_delete, sender=Profile)
def handle_deleted_profile(sender, instance, **kwargs):
if instance.shipper:
instance.shipper.delete()
if instance.carrier:
instance.carrier.delete()
if instance.affiliat:
instance.affiliat.delete()
This works only for Django 1.3 or greater because the post_delete signal was added in this Django version.
You can override the delete() method of the Profile class and delete the other objects in this method before you delete the actual profile.
Something like:
class Profile(models.Model):
# ...
def delete(self):
if self.shipper:
self.shipper.delete()
if self.carrier:
self.carrier.delete()
if self.affiliat:
self.affiliat.delete()
super(Profile, self).delete()

How to add Check Constraints for Django Model fields?

While subclassing db.models.Model, sometimes it's essential to add extra checks/constraints.
For example, I have an Event model with start_date and end_date: I want to add validation into the fields or the model so that end_date > start_date.
At least I know this can be done outside the models.Model inside the ModelForm validation. But how to attach to the fields and the models.Model?
I would not put constraints like these in the save method, it's too late. Raising an exception there, doesn't help the user who entered the data in the wrong way, because it will end up as a 500 and the user won't get the form with errors back etc.
You should really check for this in the Forms/ModelForms clean method and raise a ValidationError, so form.is_valid() returns false and you can send the errors in the form back to the user for correction.
Also note that since version 1.2, Django has had Model Validation.
It would look something like this:
class Foo(models.Model):
# ... model stuff...
def clean(self):
if self.start_date > self.end_date:
raise ValidationError('Start date is after end date')
As of Django 2.2, database level constraints are supported:
from django.db import models
from django.db.models import CheckConstraint, Q, F
class Event(models.Model):
start_date = models.DatetimeField()
end_date = models.DatetimeField()
class Meta:
constraints = [
CheckConstraint(
check = Q(end_date__gt=F('start_date')),
name = 'check_start_date',
),
]
Do it inside your save method of your model:
def save(self, *args, **kwargs):
if(self.end_date > self.start_date):
super(Foo, self).save(*args, **kwargs)
else:
raise Exception, "end_date should be greater than start_date"
As #stefanw says, it's better user experience to check in the form's clean method.
This is enough if you're very sure that there isn't, and never will be, another way to change the value. But since you can rarely be sure of that, if database consistency is important, you can add another check (in addition to the form), one of:
The easier and database-independent way is in the model's save method as #umnik700 said. Note that this still doesn't prevent other users of the database (another app, or the admin interface) from creating an inconsistent state.
To be 'completely' sure the database is consistent, you can add a database level constraint. E.g. you can create a migration with RunSQL and SQL, something like (not tested):
migrations.RunSQL('ALTER TABLE app_event ADD CONSTRAINT chronology CHECK (start_date > end_date);')
(Not tested). This may be database dependent, which is a downside of course.
In your example, it's probably not worth it (incorrect start/end times just look a bit weird, but affect only the one inconsistent event), and you don't want manual schema changes. But it's useful in cases where consistency is critical.
EDIT: You can also just save the start time and the duration, instead of the start and end times.
As of today, both postgres 9.4 and MS SQL Server >= 2008 support check constraints in sql. On top of this, there is django issue 11964 which seems to be ready for review since yesterday, so hopefully we'll see this integrated into django 2. The project rapilabs/django-db-constraints seems to implement this too.
Summarizing the answers from before, here is a complete solution I used for a project:
from django.db import models
from django.db.models import CheckConstraint, Q, F
from django.utils.translation import gettext_lazy as _
class Event(models.Model):
start_date = models.DatetimeField()
end_date = models.DatetimeField()
class Meta:
constraints = [
# Ensures constraint on DB level, raises IntegrityError (500 on debug=False)
CheckConstraint(
check=Q(end_date__gt=F('start_date')), name='check_start_date',
),
]
def clean(self):
# Ensures constraint on model level, raises ValidationError
if self.start_date > self.end_date:
# raise error for field
raise ValidationError({'end_date': _('End date cannot be smaller then start date.')})
Too bad there is no django.core.validators that can handle this :(

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...