Disallow non-blank values for certain model fields - django

As a follow up to this question I would like to know if there is way to make sure, that a model instance can not be persisted if there are any field with blank=False that have blank value. I.e. if I have this model:
class SimpleModel(models.Model):
name = models.CharField(max_length=100)
I want to disallow something like:
m = SimpleModel()
m.save()
I could do this by overriding save() and do the check manually, but maybe there is another way.
The reason for this is that I want to make really, really sure that such blank values are never inserted into the database (in case me or some of my co-coders go mad).

This is what model validation is for. You need to ensure you run m.full_clean() before save.

Related

Django: Check at model level if anything in ManyToMany field before saving

There's a lot of questions worded similarly, but every single one I've seen is somebody trying to get some kind of data through a ManyToMany relationship before saving it. I'm not trying to use the relationship at all before saving, I just want to see if the user put anything there or not.
My model has a ForeignKey field pointing to a parent model, and two ManyToMany fields pointing to other models, but I only want users to be able to use one M2M field or the other, not both. This model is being edited through the admin as an inline on its parent.
models.py
class ProductSpecial(models.Model):
# name, slug, image, etc
class ProductSpecialAmount(models.Model):
special = models.ForeignKey(ProductSpecial, related_name='amounts', on_delete=models.CASCADE)
amount = models.IntegerField()
brands = models.ManyToManyField(ProductBrand, related_name='specials', blank=True)
lines = models.ManyToManyField(ProductLine, related_name='specials', blank=True)
admin.py
class ProductSpecialAmountInline(admin.StackedInline):
model = ProductSpecialAmount
# fieldsets, etc
#admin.register(ProductSpecial)
class ProductSpecialAdmin(admin.ModelAdmin):
inlines = [ProductSpecialAmountInline]
# fieldsets, etc
I only want users to be able to choose from brands or lines, but not both, and I would like to validate this before save and throw a validation error if necessary. My initial attempt was to just do...
class ProductSpecialAmount(models.Model):
# ...
def clean(self):
if self.brands and self.lines:
raise ValidationError('Please choose either brands or lines, not both', code='invalid')
...but that throws ValueError: "<ProductSpecialAmount: ProductSpecialAmount object (None)>" needs to have a value for field "id" before this many-to-many relationship can be used.
I get that I can't actually query the related ProductBrand or ProductModel objects before this object is saved, but I don't actually want any data from those objects right now, I just want to know if the user left either of the fields blank or not, and am wondering if that's possible to see at the model level.
Whether you actually want to use the data from a field or just see if it is blank, the problem is caused by referencing the m2m field in any way before saving the object. I had a similar problem which I fixed using a custom form as per: https://stackoverflow.com/a/7986937/19837155
This might be more difficult when you're using inlines, but it may be the easiest way to solve your problem.

Concise way of getting or creating an object with given field values

Suppose I have:
from django.db import models
class MyContentClass(models.Model):
content = models.TextField()
another_field = models.TextField()
x = MyContentClass(content="Hello, world!", another_field="More Info")
Is there a more concise way to perform the following logic?
existing = MyContentClass.objects.filter(content=x.content, another_field=x.another_field)
if existing:
x = existing[0]
else:
x.save()
# x now points to an object which is saved to the DB,
# either one we've just saved there or one that already existed
# with the same field values we're interested in.
Specifically:
Is there a way to query for both (all) fields without specifying
each one separately?
Is there a better idiom for either getting the old object or saving the new one? Something like get_or_create, but which accepts an object as a parameter?
Assume the code which does the saving is separate from the code which generates the initial MyContentClass instance which we need to compare to. This is typical of a case where you have a function which returns a model object without also saving it.
You could convert x to a dictionary with
x_data = x.__dict__
Then that could be passed into the object's get_or_create method.
MyContentClass.objects.get_or_create(**x_data)
The problem with this is that there are a few fields that will cause this to error out (eg the unique ID, or the _state Django modelstate field). However, if you pop() those out of the dictionary beforehand, then you'd probably be good to go :)
cleaned_dict = remove_unneeded_fields(x_data)
MyContentClass.objects.get_or_create(**cleaned_dict)
def remove_unneeded_fields(x_data):
unneeded_fields = [
'_state',
'id',
# Whatever other fields you don't want the new obj to have
# eg any field marked as 'unique'
]
for field in unneeded_fields:
del x_data[field]
return x_data
EDIT
To avoid issues associated with having to maintain a whitelist/blacklist of fields you, could do something like this:
def remove_unneeded_fields(x_data, MyObjModel):
cleaned_data = {}
for field in MyObjModel._meta.fields:
if not field.unique:
cleaned_data[field.name] = x_data[field.name]
return cleaned_Data
There would probably have to be more validation than simply checking that the field is not unique, but this might offer some flexibility when it comes to minor model field changes.
I would suggest to create a custom manager for those models and add the functions you want to do with the models (like a custom get_or_create function).
https://docs.djangoproject.com/en/1.10/topics/db/managers/#custom-managers
This would be the cleanest way and involves no hacking. :)
You can create specific managers for specific models or create a superclass with functions you want for all models.
If you just want to add a second manager with a different name, beware that it will become the default manager if you don't set the objects manager first (https://docs.djangoproject.com/en/1.10/topics/db/managers/#default-managers)

Django making sure user and user profile have same pk

Right now I'm using Django's built in admin system to manage users, to which I've attached a profile to contain additional data using the following:
class Profile(models.Model):
user = models.OneToOneField(User, editable = False)
# Data fields here...
As it stands the User and Profile pk (and accordingly id number) will be the same if and only if the profile is created right after the user is created. I could guarantee that this would be the case during the registration process, and while that would cover most uses, creating users with the admin interface could cause mismatched ids to occur. Thus this does not seem like a very robust way to solve this problem and I'd like to hardcode the pk's to be the same. I'm not sure how to do this.
I thought the following would work:
profile_id = models.IntegerField(default=user.pk, editable = False,
primary_key = True)
But it gives me the error:
AttributeError: 'OneToOneField' has no attribute 'pk'
What's the best way to guarantee that the profile and user have the same pk? Note: I'd really rather not deal with extending the base user model as using the OneToOneField to link the two seems to be sufficient for all my needs.
Thanks!
[edit]
My reasoning for asking the question:
My immediate problem was that I wanted a dictionary of values of the User's Profile, which I was retrieving usingprofile_values = Profile.objects.filter(pk=user.id).values()[0]. This highlighted the bug, and I "hacked" around it last night using pk=user.profile.id instead. In the light of the morning this does not seem like such a terrible hack. However, it seems like having pk discrepancies could lead to quiet and hard to catch bugs down the line, and thus forcing them to match up would be a Good Idea. But I'm new to Django so I'd entirely accept that it is, in fact, never a problem if you're writing your code correctly. That said, for almost academic reasons, I'd be curious to see how this might be solved.
[/edit]
Like you already agree that it was never a problem because we have a OneToOne mapping between the two models.
So when you need to get the profile obj corresponding to a User:
profile_values = Profile.objects.get(user_id=user)
assuming,
class Profile(models.Model):
user = models.OneToOneField(User)
...
If your column name is not user, then use the corresponding name in get query.
Still if you are curious as to how to achieve same pk for both models, then we can set a signal on every save of User model. See the documentation.
def create_profile(sender, **kwargs):
if kwargs["created"]:
p = Profile(user=kwargs["instance"], ...)
p.save()
django.db.models.signals.post_save.connect(create_profile, sender=User)
create_profile() will be called every time any User object is saved.
In this function, we create Profile object only if a new User instance has been created.
If we start from blank slate, then I think this will always make sure that a Profile exists for every User and is created right after User was created; which in turn will give same pk for both models.
pk is a parameter in a filter() query, but not a field name. You probably want to use user.id.

How do I use a model field inside a queryset for a modelchoicefield?

I want to be able to seat a party at table. First I need to display the form, so I create the model form with a extra field called open_tables. This has to be every table marked AVAILABLE that has the capacity. The problem is that I don't know how to reference the number_in_party field from the queryset. I've tied self.base_fields, but self doesn't work. I've tried SeatPartyForm.model.number_in_party, that doesn't work. This is happening on the get, and number_in_party is filled at this point. Is there any way to do this query?
class SeatPartyForm( ModelForm):
open_tables = forms.ModelChoiceField(queryset=Table.objects.filter(status__exact=Table.AVAILABLE).exclude(max_capacity__lt = model.base_fields['number_in_party']))
class Meta:
model = Party
fields = ('name', 'number_in_party')`
You don't know the actual number_in_party before user fills in one. The server-side Django form cannot do the limitation automatically for you.
Thus you need to either change open_tables after number_in_party is available, via javascript; or split the form to two parts, in the first part user fills in number_in_party, in the second part, in server side you could filter open_tables according to the ready value of number_in_party.

Delete field from standard Django model

NOTE: this was asked before AbstractUser existed, which is probably what you'd want to use these days.
Basically I would like to delete the default email field from the default Django User class...
class MyUser(User):
field = models.CharField(max_length = 10)
a = 'hello'
def b(self):
print 'world'
del User.email
del MyUser.email
del Myuser.field
All these give AttributeError. Deleting methods or attributes in the following way though works fine:
del MyUser.a
del MyUser.b
So I'm curious why this doesn't work; what type of things are these model fields?
Another thing I tried was overwriting email by creating an email = None in MyUser but that didn't work either (and would be slightly more ugly).
Thanks in advance!
P.s. If you are wondering why; it's more for curiousity than that it is really necessary for the application... But I think it's good not to have unused columns in database.
P.p.s. I don't want to change the Django files to manually remove 'email' from user.
EDIT: Follow-up question here (for those who want to do the same thing) Before syncdb, delete field from standard Django model
As you've discovered, model fields aren't actually class attributes, even though they appear to be.
Models are constructed by some very clever but complicated hacking around with metaclasses. When a model class definition is executed (the first time its models.py is imported), the metaclass runs through all the field definitions for that model, and calls the contribute_to_class method of each one. This ends up putting the actual fields into the new class's _meta.fields property. So the model class itself doesn't have the fields as direct properties.
Then, when a model instance is actually instantiated, Django takes that list and directly sets the new instance's attributes, using either the arguments to the constructor or the default values supplied by the fields. So, the value that's accessed via the instance has no actual field code behind it: it's a simple instance attribute.
Anyway, the upshot of all this is that to remove a field from a model definition you just need to delete it from the Model._meta.fields list.
Since Model._meta.fields is an immutable list, you won't be able to change it directly.
You can, however, modify local_fields like this:
def remove_field(model_cls, field_name):
for field in model_cls._meta.local_fields:
if field.name == field_name:
model_cls._meta.local_fields.remove(field)
remove_field(User, "email")