Force model fields in Django - django

When creating a model in Django like this example:
class Musician(models.Model):
first_name = models.CharField(max_length=50, primary_key=True)
last_name = models.CharField(max_length=50)
instrument = models.CharField(max_length=100)
I noticed some problem (not sure if that's best word) with this approach. There is nothing preventing you from creating something like:
musician = Musician()
musician.save()
effectively having primary_key value equal to None. I would like to force user to set first_name, but frankly speaking I cannot find any simple solution for that.
Is there a way to achieve this?

First of all, don't set first_name as primary key. Just leave the default primary key as the id field. A primary key needs to be unique (a first_name isn't) and should not be something a user enters.
Second, it's true that you cannot enforce a CharField to not be empty at the database level. But you can enforce it at the code level, so that anytime you create a Django Form and validate it, it will raise an error.
In fact, Django does it automatically for you, in your case. By default first_name is a required field, since you didn't set blank=True.
So if you do:
musician = Musician()
musician.full_clean()
this raises a ValidationError.
If you create a form for your model (which is what you need if you want users to create a Musician):
class MusicianForm(ModelForm):
class Meta:
model = Musician
fields = '__all__'
form = MusicianForm(data={})
form.instance.first_name
# ''
form.is_valid()
# False
form.save()
# ValueError: The Musician could not be created because the data didn't validate.
You'll also see that if you register Musician in admin.py for django admin site, you can't leave any of the fields empty. It just won't save.

Related

How to allow a form to submit, but prevent blank fields from being saved using Django Admin 3.x?

I am using Django Admin, and have a model like this:
class Item(models.Model):
id = models.CharField(max_length=14, primary_key=True)
otherId = models.CharField(max_length=2084, blank=True)
I want id to be required and unique, and I want otherId to be optional on the Admin form, but if otherId is provided, it has to be unique.
The problem I am running into is, whenever I create an instance of Item using the Admin form and I do not provide an otherId, Django tries to save the otherId field as a blank value, but this means the second time I try to save an instance with a blank otherId value it violates the column's unique constraint and fails.
I need Django to check if the otherId field is falsey before saving, and if it is falsey, do not save that empty value along with the model. Is this possible?
You should add unique=True to otherId field.
otherid = models.CharField(max_length=2084, blank=True, null=True, unique=True)
Django ignore unique or not if otherId is blank.
I failed to understand the question very well but i think you need to override the save method of the django model and provide custom logic you stated above.
class Item(models.Model):
id = models.CharField(max_length=14, primary_key=True)
otherId = models.CharField(max_length=2084, blank=True)
def save(self, *args, **kwargs):
# handle you logic here
# check if self.id is empty string and do something about it
super(Item, self).save(*args, **kwargs)
For every model django also auto create a field id for primary key which is auto generated and incremented.
For disabling submission of blank field you must make the null and blank property False. Check the code.
Also note that the id field is automatically added in django so you need not mention that.
class Item(models.Model):
otherId = models.CharField(max_length=2084, blank=False, null=False)

Remove ForeignKey relationship

I want to remove the relationship between BUser and Profile:
Since the ForeignKey doesn't allow null values I have to iterate (performance is awful!) like this to remove all the relations:
for u in user.profile_set.all():
u.delete()
class Profile(models.Model):
user = models.ForeignKey('BUser')
class BUser(AbstractBaseUser, PermissionsMixin):
username = models.CharField(max_length=40, unique=True)
There is another way to delete all the relations [with a better performance]?
I've tried with:
obj.transparentprofile_set = None
obj.transparentprofile_set.clear()
obj.transparentprofile_set.empty()
but, like I said, since there's not null=True in the ForeignKey I can't use them.
You may use my way, instead touching related model via dotted ORM childs etc objects.childs_set, its more clear and human readable code:
# Get user instance
user = User.objects.get(pk=<uid>)
# Remove profiles
Profile.objects.filter(user=user).delete()
Also, you need remember about related_name Model parameter, so instead profile_set you can use:
class Profile(models.Model):
user = models.Foreignkey(
to=User,
related_name='profiles'
)
# Use related name alias in code
profiles = user.profiles.all()
profile = user.profiles.filter(pk=<profile_id>)

Django - ChoiceField Spanning Multiple Forms

Is it possible in django to have a ChoiceField on a formset level rather than an inline form? So for example if I have a formset for phones and each inline form represents a single Phone model, how can I have a ChoiceField that spans all the inline forms? Something like this where I'm choosing a primary phone:
My models:
class Profile(models.Model):
verified = models.BooleanField(default=False)
primary_phone = models.OneToOneField('Phone', related_name='is_primary', null=True, blank=True)
class Phone(models.Model):
profile = models.ForeignKey(Profile, editable=False)
type = models.CharField(choices=PHONE_TYPES, max_length=16)
number = models.CharField(max_length=32)
#property
def is_primary(self):
return profile.primary_phone == self
I can always remove primary_phone and use a BooleanField in Phone to indicate if it's primary or not, but I'm not sure if this going to help my problem.
I'm also looking for a less-hacky more-django-like approach if possible.
There is no way to have django create this for you automatically. In your ModelForm (that's used in the inline) I'd add a boolean field called is_primary. This field will then show up on each inlined Phone instance (as a checkbox).
On the front end sort it out with javascript so that a user can only select one default at a time. On the back end use some custom validation to double check that only one is_default was submitted, and then update the primary_phone as necessary with form logic.

Referencing a Django contrib.admin user via a foreign key?

I'm developing a small Django site and I'm using django.contrib.admin to handle content management. I'd like to capture the first name & last name of the author (an Admin user) of an Article on its initial save (and not update it if another user edits the Article).
ie.
class Article(models.Model)
title = models.CharField(max_length=50)
pub_date = models.DateTimeField('date published')
author = ForeignKey(???)
...
What do I need to write to grab this user's first name & last name fields when creating a new Article object? I'd default to their admin username if those fields are blank.
Have your model use the User object:
author = models.ForeignKey(User)
To prevent this field from being changeable on update, check out this other SO post:
Django admin: exclude field on change form only
To change the admin's Select field to use first/last name, you could try this snippet:
http://djangosnippets.org/snippets/1642/
To change the admin's view, assuming you are using the built-in templates, you could add a custom column as described on this post: How do I add a custom column with a hyperlink in the django admin interface?
class AuthorAdmin(admin.ModelAdmin):
list_display = ('author_name',)
def my_author_name(self, obj):
if obj.author.first_name and obj.author.last_name:
return '%s %s' % (obj.author.first_name, obj.author.last_name)
else:
return obj.author.username
my_author_name.allow_tags = True
my_author_name.short_description = 'Author'
I think you are looking for this:
author = models.ForeignKey(User)
It looks like the best way to handle a None or blank result from get_full_name is to just populate User.author with models.ForeignKey(User) and then — at the template level — use the following:
{{ user.get_full_name|default:user.username }}
... via this SO answer. This allows me to perform queries on a User's Articles, but still gracefully handles blank first_name & last_name fields if a User hasn't entered them yet, but will also update dynamically when they have).

Django Form with no required fields

I want to make a form used to filter searches without any field being required. For example given this code:
models.py:
class Message(models.Model):
happened = models.DateTimeField()
filename = models.CharField(max_length=512, blank=True, null=True)
message = models.TextField(blank=True, null=True)
dest = models.CharField(max_length=512, blank=True, null=True)
fromhost = models.ForeignKey(Hosts, related_name='to hosts', blank=True, null=True)
TYPE_CHOICES = ( (u'Info', u'Info'), (u'Error', u'Error'), (u'File', u'File'), (u'BPS', u'BPS'),)
type = models.CharField(max_length=7, choices=TYPE_CHOICES)
job = models.ForeignKey(Jobs)
views.py:
WHEN_CHOICES = ( (u'', ''), (1, u'Today'), (2, u'Two days'), (3, u'Three Days'), (7, u'Week'),(31, u'Month'),)
class MessageSearch(ModelForm): #Class that makes a form from a model that can be customized by placing info above the class Meta
message = forms.CharField(max_length=25, required=False)
job = forms.CharField(max_length=25, required=False)
happened = forms.CharField(max_length=14, widget=forms.Select(choices=WHEN_CHOICES), required=False)
class Meta:
model = Message
That's the code I have now. As you can see it makes a form based on a model. I redefined message in the form because I'm using an icontains filter so I didn't need a giant text box. I redefined the date mostly because I didn't want to have to mess around with dates (I hate working with dates! Who doesnt?) And I changed the jobs field because otherwise I was getting a drop down list of existing jobs and I really wanted to be able to search by common words. So I was able to mark all of those as not required
The problem is it's marking all my other fields as required because in the model they're not allowed to be blank.
Now in the model they can't be blank. If they're blank then the data is bad and I don't want it in the DB. However the form is only a filter form on a page to display the data. I'm never going to save from that form so I don't care if fields are blank or not. So is there an easy way to make all fields as required=false while still using the class Meta: model = Message format in the form? It's really handy that I can make a form directly from a model.
Also this is my first serious attempt at a django app so if something is absurdly wrong please be kind :)
You can create a custom ModelForm that suit your needs. This custom ModelForm will override the save method and set all fields to be non-required:
from django.forms import ModelForm
class SearchForm(ModelForm):
def __init__(self, *args, **kwargs):
super(SearchForm, self).__init__(*args, **kwargs)
for key, field in self.fields.iteritems():
self.fields[key].required = False
So you could declare your forms by simply calling instead of the ModelForm, e.g.:
class MessageForm(SearchForm):
class Meta:
model = Message
You could also pass empty_permitted=True when you instantiate the form, e.g.,
form = MessageSearch(empty_permitted=True)
that way you can still have normal validation rules for when someone does enter data into the form.
I would give a try to the django-filter module :
http://django-filter.readthedocs.io/en/develop/
fields are not required. these are filters actually. It would look like this :
import django_filters
class MessageSearch(django_filters.FilterSet):
class Meta:
model = Message
fields = ['happened', 'filename', 'message', '...', ]
# django-filter has its own default widgets corresponding to the field
# type of the model, but you can tweak and subclass in a django way :
happened = django_filters.DateFromToRangeFilter()
mandatory, hidden filters can be defined if you want to narrow a list of model depending on something like user rights etc.
also : setup a filter on a 'reverse' relationship (the foreignkey is not in the filtered model : the model is referenced elsewhere in another table), is easy, just name the table where the foreign key of the filtered model field is :
# the 'tags' model has a fk like message = models.ForeignKey(Message...)
tags= django_filters.<some filter>(name='tags')
quick extendable and clean to setup.
please note I didn't wrote this module, I'm just very happy with it :)