Django multiple models, one form - django

I have a model that looks like this:
class Movie(models.Model):
title = models.CharField(max_length=200)
slug = models.SlugField(max_length=200)
user = models.ForeignKey(User)
created_on = models.DateTimeField(default=datetime.datetime.now())
class Meta:
ordering = ['-title']
def __unicode__(self):
return self.title
class MovieScreener(models.Model):
screener_asset = models.FileField(upload_to='movies/screeners/')
movie = models.ForeignKey(Movie)
class MovieTrailer(models.Model):
trailer_asset = models.FileField(upload_to='movies/trailers/', blank=True, null=True)
description = models.TextField()
movie = models.ForeignKey(Movie)
class MoviePoster(models.Model):
poster_asset = models.FileField(upload_to='movies/posters/', blank=True, null=True)
movie = models.ForeignKey(Movie)
And my forms look like this:
class MovieForm(forms.ModelForm):
class Meta:
model = Movie
exclude = ('user','created_on')
class MovieScreenerForm(forms.ModelForm):
class Meta:
model = MovieScreener
exclude = ('movie',)
class MovieTrailerForm(forms.ModelForm):
class Meta:
model = MovieTrailer
exclude = ('movie',)
class MoviePosterForm(forms.ModelForm):
class Meta:
model = MoviePoster
exclude = ('movie',)
And here is my views.py (this is where it looks ugly)
#login_required
def create_movie(request, template_name="explore/create_movie.html"):
if request.method == 'POST':
movie_form = MovieForm(data=request.POST)
movie_screener_form = MovieScreenerForm(data=request.POST, files=request.FILES, prefix="a")
movie_trailer_form = MovieTrailerForm(data=request.POST, files=request.FILES, prefix="b")
movie_poster_form = MoviePosterForm(data=request.POST, files=request.FILES, prefix="c")
if movie_form.is_valid() and movie_screener_form.is_valid() and movie_trailer_form.is_valid():
movie_form.instance.user = request.user
movie = movie_form.save()
movie_screener_form.save(commit=False)
movie_screener_form.instance.movie = movie
movie_screener_form.save()
movie_trailer_form.save(commit=False)
movie_trailer_form.instance.movie = movie
movie_trailer_form.save()
movie_poster_form.save(commit=False)
movie_poster_form.instance.movie = movie
movie_poster_form.save()
url = urlresolvers.reverse('explore')
return redirect(url)
else:
movie_form = MovieForm(instance=request.user, label_suffix='')
movie_screener_form = MovieScreenerForm(prefix="a", label_suffix='')
movie_trailer_form = MovieTrailerForm(prefix="b", label_suffix='')
movie_poster_form = MoviePosterForm(prefix="c", label_suffix='')
context = RequestContext(request, locals())
return render_to_response(template_name, context)
My views.py seems very repetitive, is this the right way to do this or is there a better way to do this?
Thanks
J

Can't really think of a way in terms of defining the models or forms, but you can cut down on some lines with the following.
mfs = [movie_screener_form, movie_trailer_form, movie_poster_form]
for mf in mfs:
mf.save(commit=False)
mf.instance.movie = movie
mf.save()

One thing you could do is move the setting of the movie instance on model forms that require it from the view to the form itself by passing it in as an argument when initializing the form. Here's an example of one implementation, but this can probably be made into a base form class that others can inherit from, saving you from having to do the override in each one individually. None of this code has been tested, I'm just thinking out loud...
class MovieScreenerForm(forms.ModelForm):
class Meta:
model = MovieScreener
exclude = ('movie',)
def __init__(self, movie, *args, **kwargs):
super(MovieScreen, self).__init__(*args, **kwargs)
self.movie = movie
def save(self, commit=True):
instance = super(MovieScreenerForm, self).save(commit=False)
instance.move = self.movie
instance.save()

If I've understood your design correctly, then:
Every movie has a screener
Movies never have more than one screener
A movie might have a trailer
Movies never have more than one trailer
A movie might have a poster
Movies never have more than one poster
Is this right?
If my assumptions are right, then you can just have all the fields on the Movie model. (The trailer and poster are already nullable, so they are optional). So you only need one model and one form.

Related

Cannot assign "id": "Product.category" must be a "CategoryProduct" instance

i'm working on a django project and i got this error (Cannot assign "'11'": "Product.category" must be a "CategoryProduct" instance.) anyone here can help me please.
Model:
class Product(models.Model):
name = models.CharField("Nombre", max_length=150)
category = models.ForeignKey(CategoryProduct, on_delete=models.SET_NULL, null=True, related_name='category')
def __str__(self):
return self.name
View:
class ProductCreateView(CreateView):
model = Product
form_class = ProductForm
success_url = '/adminpanel/products/'
def post(self, request, *args, **kwargs):
form = self.get_form()
category = CategoryProduct.objects.get(id=request.POST['category'])
if form.is_valid():
product = form.save(commit=False)
product.category = category
product.save()
Form:
class ProductForm(forms.ModelForm):
name = forms.CharField(max_length=150, label="Nombre")
category = forms.ChoiceField(choices=[(obj.id, obj.name) for obj in CategoryProduct.objects.all()], label="Categoría")
class Meta:
model = Product
fields = ['name', 'category']
You can let Django's ModelForm do its work, this will create a ModelChoiceField [Django-doc], which is where the system gets stuck: it tries to assign the primary key to category, but that should be a ProductCategory object, so you can let Django handle this with:
class ProductForm(forms.ModelForm):
class Meta:
model = Product
fields = ['name', 'category']
If you want to specify a different label, you can use the verbose_name=… [Django-doc] from the model field, or specify this in the labels options [Django-doc] of the Meta of the ProductForm. So you can specify Categoria with:
class Product(models.Model):
name = models.CharField('Nombre', max_length=150)
category = models.ForeignKey(
CategoryProduct,
on_delete=models.SET_NULL,
null=True,
related_name='products',
verbose_name='Categoria'
)
def __str__(self):
return self.name
then the CreateView can just use its boilerplate logic:
class ProductCreateView(CreateView):
model = Product
form_class = ProductForm
success_url = '/adminpanel/products/'
Note: The related_name=… parameter [Django-doc]
is the name of the relation in reverse, so from the Category model to the Product
model in this case. Therefore it (often) makes not much sense to name it the
same as the forward relation. You thus might want to consider renaming the category relation to products.

Django: Form and many2many relationship using through relationshiop

I am trying to sort out a specific problem that involve "many2many" relationship using through specification.
I've already tried to use inline_factory but I was not able to sort out the problem.
I have these tables
class Person(models.Model):
id = models.AutoField(primary_key=True)
fullname = models.CharField(max_length=200)
nickname = models.CharField(max_length=45, blank=True)
class Meta:
db_table = 'people'
class Role(models.Model):
role = models.CharField(max_length=200)
class Meta:
verbose_name_plural = 'roles'
db_table = 'roles'
class Study(models.Model):
id = models.AutoField(primary_key=True)
title = models.CharField(max_length=255)
description = models.CharField(max_length=1000)
members = models.ManyToManyField(Person, through='Studies2People')
class Meta:
db_table = 'studies'
class Studies2People(models.Model):
person = models.ForeignKey(Person)
role = models.ForeignKey(Role)
study = models.ForeignKey(Study)
class Meta:
verbose_name_plural = 'studies2people'
db_table = 'studies2people'
unique_together = (('person', 'role', 'study'),)
#forms.py
from .models import Study, Person, Role, Studies2People
class RegisterStudyForm(ModelForm):
class Meta:
model = Study
fields = '__all__'
#View.py
class StudyCreateView(CreateView):
template_name = 'managements/register_study.html'
model = Study
form_class = RegisterStudyForm
success_url = 'success/'
def get(self, request, *args, **kwargs):
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
return self.render_to_response(self.get_context_data(form=form))
The code above creates a form like:
Study.Title
Study.description
List of People
I want to create a form to fill in all fields that involve Studies2People Something like this:
Study.Title
Study.description
Combo(people.list)
Combo(Role.list)
Maybe I should start from Studies2People but I don't know how to show the "inline" forms involved.
Thanks in advance
C.
waiting someone that is able to explain with some examples the relationship m2m with through (model & view), I sorted out my problem in a different way.
I've created three forms.
1 Model Form (study)
2 Form (forms with ModelChoiceField(queryset=TableX.objects.all())
Created a classView to manage the get and post action.(validation form too)
In the post procedure I used "transaction" to avoid "fake" data.
I hope that someone will post an example with complex m2m relationships.
Regards
Cinzia

How do I limit the ListView slug to a number of choices in Django?

Here is the code
models.py
class Submission(models.Model):
CAR = 'car'
TRUCK = 'truck'
VAN = 'van'
SUV = 'suv'
CAR_TYPES = (
(CAR, 'Car'),
(TRUCK, 'Truck'),
(VAN, 'Van'),
(SUV, 'SUV'),
)
submission_type = models.CharField(_('Submission Type'), max_length=20, choices=MEDIA_TYPES, default=CAR)
title = models.CharField(_('Title'), max_length=100, blank=False)
description = models.TextField(_('Description'))
user = models.ForeignKey(User, related_name='user_submission')
thumbnail = models.ImageField()
date_submitted = models.DateTimeField(default=timezone.now)
views.py
class SubmissionCategoryList(ListView):
model = Submission
template_name = 'submission/submit_cat.html'
def get_queryset(self):
queryset = super(SubmissionCategoryList, self).get_queryset()
return queryset.filter(submission_type=self.kwargs['slug']).order_by('-date_submitted')
def get_context_data(self, **kwargs):
context = super(SubmissionCategoryList, self).get_context_data(**kwargs)
return context
urls.py
url(r'^(?P<slug>[\w-]+)/$', SubmissionCategoryList.as_view(), name='submit_category'),
The code works fine. When I go to localhost:8000/car/ It shows the list view for only the CARS submission_type, etc. But, when I type in a url that isn't a part of the choices in CAR_TYPES, for example, localhost:8000/boat/, django still shows the template for this view. My question is: How do I limit the number of choices the slug should accept? And, if it is not a part of the CAR_TYPES choices, how do I get it to ignore this view?
Have you tried validating your "slug" in views?
views.py
def get_queryset(self):
if self.kwargs['slug'] is not None and self.kways['slug'].lower() in project.settings.CAR_TYPE:
# return your queryset.filter ...
else:
# return other template or redirect to other views.
project.settings.py
CAR_TYPE = ['car', 'truck', 'van', 'suv']
or, simply just edit url regex:
(?P<slug>car|truck|van|suv)
not the best approach but definitely solve your problem.
Ok so thanks to Anzel I figured out a great solution to this problem. I'm putting this here for anyone who needs this.
I created a reference.py file that looks like this. You can call it whatever you like, I just call it reference because it's what it does.
from enum import Enum
class CarTypes(Enum):
CAR = 'car'
TRUCK = 'truck'
VAN = 'van'
SUV = 'suv'
I changed my models.py to import reference.py
I changed choices in submission_type to choices=tuple([(auto.name, auto.value) for auto in CarTypes])
from .reference import CarTypes
class Submission(models.Model):
submission_type = models.CharField(_('Submission Type'), max_length=20, choices=tuple([(auto.name, auto.value) for auto in CarTypes]), default=CarTypes.CAR)
title = models.CharField(_('Title'), max_length=100, blank=False)
description = models.TextField(_('Description'))
user = models.ForeignKey(User, related_name='user_submissions')
thumbnail = models.ImageField()
date_submitted = models.DateTimeField(default=timezone.now)
And here is the final working views.py for categories
from .reference import CarTypes
class SubmissionCategoryList(ListView):
model = Submission
template_name = 'submission/submit_cat.html'
CAR_TYPES = [auto.value for auto in CarTypes]
def get_queryset(self):
if self.kwargs['slug'] in self.CAR_TYPES:
queryset = super(SubmissionCategoryList, self).get_queryset()
return queryset.filter(submission_type=self.kwargs['slug']).order_by('-date_submitted')
else:
return Http404
def get_context_data(self, **kwargs):
context = super(SubmissionCategoryList, self).get_context_data(**kwargs)
context['submission_type'] = self.kwargs['slug']
return context
You can put this same queryset in a DetailView and it should work when a user searches site.com/car/2 or something like that

django manytomany field using through and formwizard

I am trying to create a pretty complicated form and break it up using formwizard. The first thing I am trying to do is get the ManyToManyField using through to display, Then I need to figure out how to make it all save.
#models.py
----------------------
class Meat(models.Model):
name = models.charField(max_length=200)
company = models.CharField(max_length = 200)
class Starch(models.Model):
name = models.CharField(max_length=200)
company = models.CharField(max_length=200)
class Recipe(models.Model):
name = models.CharField(max_length=200)
description = models.TextField(help_text='Please describe the finished dish')
meat = models.ManyToManyField('Meat' through='RecipeMeat')
meat_notes = models.TextField()
starch = models.ManyToManyField('Starch' through='RecipeStarch')
starch_notes = models.TextField()
class RecipeMeat(models.Model):
recipe = models.ForeignKey(Recipe)
meat = models.ForeignKey(Meat)
qty = models.FloatField()
class RecipeStarch
recipe = models.ForeignKey(Recipe)
starch = models.ForeignKey(Starch)
qty = models.FloatField()
.
#forms.py
-------------------
class RecipeForm(forms.ModelForm):
class Meta:
model = Recipe
fields = ('name', 'description')
class RecipeMeatForm(forms.ModelForm):
class Meta:
model = RecipeMeat
class RecipeMeatNotesForm(forms.ModelForm):
class Meta:
model = Recipe
fields = ('meat_notes',)
class RecipeStarch(forms.ModelForm):
class Meta:
model = RecipeStarch
class RecipeStarchNotesForm(forms.ModelForm):
class Meta:
model = Recipe
fields = ('starch_notes')
MeatFormSet = inlineformset_factory(Recipe, RecipeMeat, form=RecipeMeatForm, extra=1)
.
#views.py
---------------------------
class CreateRecipeWizard(SessionWizardView):
template_name = "create-recipe.html"
instance = None
file_storage = FileSystemStorage(location= 'images')
def dispatch(self, request, *args, **kwargs):
self.instance = Recipe()
return super(CreateRecipeWizard, self).dispatch(request, *args, **kwargs)
def get_form_instance( self, step ):
return self.instance
def done( self, form_list, **kwargs ):
self.instance.save()
return HttpResponseRedirect(reverse(all-recipes))
.
#urls.py
------------------------------
url(r'^create-recipe/$', views.CreateRecipeWizard.as_view([RecipeForm, MeatFormSet, RecipeMeatNotesForm, RecipeStarchNotesForm]), name='create-recipe'),
.
I am a bit of a rookie with this django stuff. The Recipe part is much longer and more complicated but pretty much the same pattern. If any one could help point me in the right on how to get my ManyToManyField using through part figured out or pointed in the right direction it would be greatly appreciated.
To save the ManyToMany relationship on a formwizard process you can do something like this;
def done(self, form_list, **kwargs):
form_data_dict = self.get_all_cleaned_data()
m2mfield = form_data_dict.pop('m2mfield')
instance = form_list[0].save()
for something in m2mfield:
instance.m2mfield.add(something)
return render_to_response(
'done.html', {},
context_instance=RequestContext(self.request)
)
In this example the first form in the list is a ModelForm for the thing I'm trying to create and it has a ManyToManyField to another model for which I have a form second in the process. So I grab that first form & save it, then grab the field from the cleaned data from the second form and save the selected options to the M2M field.

How to save a ManyToMany relationship from a ModelForm in Django

I'm not sure to save my ManyToMany relationship. I found my exact problem in this thread: Django embedded ManyToMany form, except instead of Sales and Products models, I have models that make up a movie.
I tried the solution, but I receive a syntax error. I don't understand how Django should link the EquipmentModel, LightModel, and ActorModel to the ManyToMany relationship in MovieModel. So far (before trying the other thread's solution), the CharFields that are displayed on the form for LightModel, EquipmentModel, and ActorModel are not linked to the ManyToManyField in MovieModel. So when I save the forms and try to access a particular Movie's actors, all I see is a blank list. The solution from the other thread seems to make sense since it tries to link the models to the ManyToMany relationship in MovieModel, but I don't understand how Django knows which MovieModel to add to (how does it get the correct movieID?).
On a side note, is there a way to check for duplicate movies when the user presses the 'Submit' button on the form? I want to avoid creating duplicates.
views.py:
def add_movie(request, movieID=""):
if request.method == "POST":
form = MovieModelForm(request.POST)
eform = EquipmentModelForm(request.POST)
lform = LightModelForm(request.POST)
aform = ActorModelForm(request.POST)
print 'checking form'
print request.POST.items()
if form.is_valid() and eform.is_valid() and lform.is_valid() and aform.is_valid():
print 'form is valid'
movie_to_add = form.save()
e = eform.save()
l = lform.save()
a = aform.save()
movie_to_add.actors.add(a)
movie_to_add.lights.add(l)
movie_to_add.equipments.add(e)
# return HttpResponseRedirect('/data')
else:
# code for create forms ....
return render_to_response('add_movie.html', {'form':form, 'eform':eform,'lform':lform, 'aform':aform,}, context_instance=RequestContext(request))
Other code that may help:
forms.py
class LightModelForm(forms.ModelForm):
class Meta:
model = LightModel
class ActorModelForm(forms.ModelForm):
class Meta:
model = ActorModel
class EquipmentModelForm(forms.ModelForm):
class Meta:
model = EquipmentModel
class MovieModelForm(forms.ModelForm):
class Meta:
model = MovieModel
fields = ("title", "rank")
models.py
class EquipmentModel(models.Model):
equip = models.CharField(max_length=20)
class ActorModel(models.Model):
actor = models.CharField(max_length=20)
class LightModel(models.Model):
light = models.CharField(max_length=20)
class MovieModel(models.Model):
rank = models.DecimalField(max_digits=5000, decimal_places=3)
title = models.CharField(max_length=20)
equipments = models.ManyToManyField(EquipmentModel, blank=True, null=True)
actors = models.ManyToManyField(ActorModel, blank=True, null=True)
lights = models.ManyToManyField(LightModel, blank=True, null=True)
def __str__(self):
return self.title
Edit: removed unnecessary init and fields thanks to DTing
Edit2: Fixed!
There is a whole lot of stuff going wrong here in addition to what spulec said.
Your models.py look okay.
class EquipmentModel(models.Model):
equip = models.CharField(max_length=20)
class ActorModel(models.Model):
actor = models.CharField(max_length=20)
class LightModel(models.Model):
light = models.CharField(max_length=20)
class MovieModel(models.Model):
rank = models.DecimalField(max_digits=5000, decimal_places=3)
title = models.CharField(max_length=20)
equipments = models.ManyToManyField(EquipmentModel, blank=True, null=True)
actors = models.ManyToManyField(ActorModel, blank=True, null=True)
lights = models.ManyToManyField(LightModel, blank=True, null=True)
def __str__(self):
return self.title
You don't need to override the __init__ method on forms if you are not changing anything on init. You also don't need to be explicit about the fields if you want to include them all.
class LightModelForm(forms.ModelForm):
class Meta:
model = LightModel
class ActorModelForm(forms.ModelForm):
class Meta:
model = ActorModel
class EquipmentModelForm(forms.ModelForm):
class Meta:
model = EquipmentModel
class MovieModelForm(forms.ModelForm):
class Meta:
model = MovieModel
fields = ("title", "rank")
your view doesn't really make sense unless for every movie you are trying to add you also want to:
add a new movie to the db using the submitted post data
create one actor object and add to db
create one light object and add to db
create one equipment object and add to db
take those three objects and add them to another movie's m2m relationships.
This other movie is some movie that you pulled from the urlconf and passed to your view, not the one you just created.
This all seems a little strange.
what i think you want to do is create all the equipment, actors and lights objects so they are in your db already, and use the default m2m formfield widget to select them when adding a movie.
so:
forms.py
class MovieModelForm(forms.ModelForm):
class Meta:
model = MovieModel
urls.py:
url(r'^add_movie/$', add_movie)
views.py:
def add_movie(request):
if request.method=='POST':
form = MovieModelForm(request.POST)
if form.is_valid():
form.save()
return HttpResponse('success')
else:
form = MovieModelForm()
context = {'form':form }
return render_to_response('some_template.html', context,context_instance=RequestContext(request))
you could combine adding actors, lights, and equipment into the same form but that's a bit much for me to write out right now.
As far as modifying your original code to add those lights, actors, and equipment to the movie you just created, you could do this:
if form.is_valid() and eform.is_valid() and lform.is_valid() and aform.is_valid():
new_movie = form.save()
e = eform.save()
l = lform.save()
a = aform.save()
new_movie.actors.add(a)
new_movie.lights.add(l)
new_movie.equipments.add(e)
Change it to:
movie_to_add = get_object_or_404(MovieModel, id=movieID)