I'm super new to Django, so bare with me. I'm trying to write a simple CRUD, using modelforms. In the update view, the form object initialization takes the arguments initial and instance, which confuses me. From django doc:
"As with regular forms, it’s possible to specify initial data for forms by specifying an initial parameter when instantiating the form. Initial values provided this way will override both initial values from the form field and values from an attached model instance."
Which confuses even more. I know my question isn't specific, but if someone could explain this and honestly, the background connection between the model and modelforms I would really appreciate it. Thanks y'all
Here is the example from Django docs;
from django.db import models
from django.forms import ModelForm
TITLE_CHOICES = [
('MR', 'Mr.'),
('MRS', 'Mrs.'),
('MS', 'Ms.'),
]
class Author(models.Model):
name = models.CharField(max_length=100)
title = models.CharField(max_length=3, choices=TITLE_CHOICES)
birth_date = models.DateField(blank=True, null=True)
def __str__(self):
return self.name
class AuthorForm(ModelForm):
class Meta:
model = Author
fields = ['name', 'title', 'birth_date']
Your form -AuthorForm has information that name is string and it can't be greater than 100 characters. Because it's in your model -Author. Same for title and birth_date. title can't be greater than 3 characters and it must be one of the MR, MRS or MS.
You don't have to specify types, rules for form fields if you use model forms. Model forms can create forms quickly from your model and makes validations based on your model.
Let's assume you have an author instance which has name is Joe.
If you print author.name, it returns Joe.
form = AuthorForm(initial={'name': 'Patrick'}, instance=author)
If you print form['name'].value(), it returns Patrick, not Joe. It ignores author's name value. You overrided it in your form. It could be Joe if you didn't pass initial parameter.
Related
I am working through a tutorial that includes the building of an articles app. I have an Article model that I am serializing and I am curious about why I need to explicitly set certain fields when using a ModelSerializer.
Here is my model:
from django.db import models
from core.models import TimestampedModel
class Article(TimestampedModel):
slug = models.SlugField(db_index=True, max_length=255, unique=True)
title = models.CharField(db_index=True, max_length=255)
description = models.TextField()
body = models.TextField()
author = models.ForeignKey('profiles.Profile', on_delete=models.CASCADE, related_name='articles')
def __str__(self):
return self.title
Pretty standard stuff. Next step is to serialize the model data in my serializers.py file:
class ArticleSerializer(serializers.ModelSerializer):
author = ProfileSerializer(read_only=True) # Three fields from the Profile app
description = serializers.CharField(required=False)
slug = serializers.SlugField(required=False)
class Meta:
model = Article
fields = (
'author',
'body',
'createdAt',
'description',
'slug',
'title',
'updatedAt',
)
Specifically, why do I need to explicitly state the author, description, and slug fields if I am using serializers.ModelSerializer and pulling those fields in from my model in my class Meta: below?
Thanks!
In the Django-Rest-Framework documentation, drf-docs/model_serializer/specifying-which-fields-to-include it says:
If you only want a subset of the default fields to be used in a model serializer, you can do so using fields or exclude options, just as you would with a ModelForm. It is strongly recommended that you explicitly set all fields that should be serialized using the fields attribute. This will make it less likely to result in unintentionally exposing data when your models change.
Therefore by using fields = in the Serializer META, you can specify just the needed fields, and not returning vital fields like id, or exessive information like updated and created timestamps.
You can also instead of using fields, use exclude, which again takes in a tuple, but just excludes the fields you don't want.
These are especially useful when your database table contains a lot of information, returning all this information, especially if it is listed, can result in large return JSON's, where the frontend may only use a small percentage of the sent data.
DRF has designed their framework like this to specifically combat these problems.
In my opinion, we should define field in serializer for:
Your api use serializer don't need all data of your models. Then you can limit field can get by serializer. It faster if you have so much data.
You dont want public all field of your model. Example like id
Custom field in serializer like serializers.SerializerMethodField() must define in fields for work
Finally, iF you dont want, you can define serializer without define fields. Its will work normally
In order to be sure I force users to pick a valid value from a dropdown (rather than unknowingly leaving the first option in the list set without changing it to the correct value), I am inserting the blank choice field in many required form fields.
models.py
class MyModel(models.Model):
gender = models.CharField(max_length=1, null=True, blank=False, choices=GENDER_CHOICES, default='')
Original forms.py (Explicitly defining the form field. This works.)
from django.db.models.fields import BLANK_CHOICE_DASH
class MyForm(forms.ModelForm):
gender = forms.TypedChoiceField(required=True, choices=BLANK_CHOICE_DASH+MyModel._meta.get_field('gender').choices)
class Meta:
model = MyModel
fields = '__all__'
Now, because I am doing this for many fields, I tried to revise my earlier code in forms.py:
Revised forms.py (Setting the choices in __init__(). Does not work.)
class MyForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
for type in ('gender', ...):
self.fields[type].choices.insert(0, (u'',u'---------' ))
# Not needed anymore.. replaced by code in __init__()
# gender = forms.TypedChoiceField(required=True, choices=BLANK_CHOICE_DASH+MyModel._meta.get_field('gender').choices)
class Meta:
model = MyModel
fields = '__all__'
In this case, I do not get the dashes as the first choice in the dropdown.
I tried to look into the problem by using pdb in the template to inspect the form field right before it was outputted. The debugger shows element.field.choices to be (u'', u'---------'), ('f', 'Female'), ...] in both cases. Nevertheless, the code that outputs the form field inserts the dashes only in the original code, not in the revised code.
I tried stepping through the Django code that renders the form field to figure out if it was using some other field besides .choices that I'm not aware of, but I never stepped into the right part of the code.
Any ideas how to accomplish this? Although it works fine the first way, the revised code is much DRY-er... if only I could make it work! Thanks!
I have several models with several fields in my app. I want to set up a way for the user to be able to modify a help text system for each field in the model. Can you give me some guidance on how to design the models, and what field types to use? I don't feel right about storing the model and field name in CharFields, but if that is the only way, I may be stuck with it.
Is there a more elegant solution using Django?
For a quick and silly example, with an app named jobs, one named fun, and make a new app named helptext:
jobs.models.py:
class Person(models.Model):
first_name = models.CharField(max_length=32)
.
.
interests = models.TextField()
def __unicode__(self):
return self.name
class Job(models.Model):
name = models.CharField(max_length=128)
person = models.ForeignKey(Person)
address = models.TextField()
duties = models.TextField()
def __unicode__(self):
return self.name
fun.models.py:
class RollerCoaster(models.Model):
name = models.CharField(max_length=128)
scare_factor = models.PositiveInteger()
def __unicode__(self):
return self.name
class BigDipper(RollerCoaster):
max_elevation = models.PositiveInteger()
best_comment_ever_made = models.CharField(max_length=255)
def __unicode__(self):
return super.name
Now, let's say I want to have editable help text on Person.interests, and Job.duties, RollerCoaster.scare_factor, and BigDipper.best_comment_ever_made. I'd have something like:
helptext.models.py:
from django.contrib.contenttypes.models import ContentType
class HelpText(models.Model):
the_model = models.ForeignKey(ContentType)
the_field = models.CharField(max_length=255)
helptext = models.CharField(max_length=128)
def __unicode__(self):
return self.helptext
So, what is the better way to do this, other than making HelpText.the_model and HelpText.the_field CharFields that have to be compared when I am rendering the template to see if helptext is associated with each field on the screen?
Thanks in advance!
Edit:
I know about the help_text parameter of the fields, but I want this to be easily edited through the GUI, and it may contain a LOT of help with styling, etc. It would be HTML with probably upwards of 50-60 lines of text for probably 100 different model fields. I don't want to store it in the field definition for those reasons.
I changed the HelpText model to have a reference to ContentType and the field a CharField. Does this seem like a good solution? I am not sure this is the most elegant way. Please advise.
Edit 2013-04-19 16:53 PST:
Currently, I tried this and it works, but not sure this is great:
from django.db import models
from django.contrib.contenttypes.models import ContentType
# Field choices for the drop down.
FIELDS = ()
# For each ContentType verify the model_class() is not None and if not, add a tuple
# to FIELDS with the model name and field name displayed, but storing only the field
# name.
for ct in ContentType.objects.all():
m = ct.model_class()
if m is not None:
for f in ct.model_class()._meta.get_all_field_names():
FIELDS += ((f, str(ct.model) + '.' + str(f)),)
# HelpText model, associated with multiple models and fields.
class HelpText(models.Model):
the_model = models.ForeignKey(ContentType)
the_field = models.CharField(max_length=255, choices=FIELDS)
helptext = models.TextField(null=True, blank=True)
def __unicode__(self):
return self.helptext
Doesn't feel like the best, but please advise if this is a solution that will bite me in the behind later on and make me filled with regrets... :*(
The solution works, and I have it implemented, but you have to be aware that sometimes the ContentTypes get out of sync with your models. You can manually update the content types with this:
python manage.py shell
>>> from django.contrib.contenttypes.management import update_all_contenttypes
>>> update_all_contenttypes(interactive=True)
This allows you to add the new ones and remove the old ones, if they exist.
The nice thing about the Field not being a foreign key is that I can put anything in it for help text. So, say I have a field "First Name." I can put a helptext connected to the Person model and the "first_name" field. I can also make something up, like "Something really confusing." The helptext is now associated with the Person model and the "Something really confusing" field. So, I can put it at the top of the form, instead of associating to a field with hard foreign keying. It can be anything arbitrary and will follow with that "field" anywhere. The hangup would be that you may change the name of the helptext field association inadvertently sending your original helptext into never land.
To make this easy, I created a TemplateTag, which I pass the name of the model and the name of the "field" I want to associate. Then anytime the template is rendered, that helptext is there, editable for anybody to get assistance with their user interface forms.
Not sure this is the best solution, but I couldn't really see any other way to do it, and got no responses.
Cheerio!
Assume these as django models:
class Author():
name = charfield()
class Book():
title = charfield()
author = foreignkey()
and a ModelForm:
class BookForm():
# i override the author field
author = models.CharField()
class Meta:
model = Book
fields = ('title', 'author')
I'm using an autocomplete plugin (like facebook search, so i can fill the author field without using choicefield).The problem is, i cannot assign value from author field because it is not an instance of author, it is a string. So i'm thinking to manipulate it before it gets validated. I tried modifying the QueryDict but is immutable. As soon as i invoke the form with form = BookForm(request.POST) it gets the error : Cannot assign u"foo":"Book.author" must be an Author instance. Thank you.
You are not doing it the "django way". you need to write your own widget for the author field.
and do something like this:
class BookForm():
class Meta:
model = Book
fields = ('title', 'author')
widgets = {
'author': YourFacebookLikeWidgetClass,
}
Now, your widget is responsible for doing all your cool stuff, (ajax call, rendering the results...etc) , but in the end, it will return the right value for the form. (the author ID)
Read more about django widgets
The problem is that there isnt a lot of info about writing your own widgets, but you can always ask here, and watch the source code of some django widgets. (It is not vert complicated to write your own :) )
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 :)