I'm trying to use Django ModelForms to allow teachers to schedule weekly classes with students. However, whenever I save a new instance of the weekly class from the modelform the m2m relations (students and teachers) are not saved.
I've looked pretty extensively in the Django documentation and tried everything to remedy this including setting commit=False on the save method and then using the save_m2m method. No dice.
Here's my code
models.py
class WeeklyClass(models.Model):
status = models.CharField(
max_length=1,
choices=STATUS_CHOICES,
default="A")
students = models.ManyToManyField(
Profile,
limit_choices_to=models.Q(is_teacher=False),
related_name='student_weekly_classes',)
teachers = models.ManyToManyField(
Profile,
limit_choices_to=models.Q(is_teacher=True),
related_name='teacher_weekly_classes',)
class Meta:
verbose_name = 'Class'
verbose_name_plural = 'Classes'
ordering = ["-created"]
forms.py
class WeeklyClassForm(ModelForm):
class Meta:
model = WeeklyClass
fields = [
"status",
"students",
"teachers",
"weekday",
"duration_hours",
"hour",
"minute"]
views.py
#login_required
def new_weekly_class(request):
if request.method == "POST":
form = WeeklyClassForm(request.POST)
if form.is_valid():
form.save()
return redirect(reverse(
"weekly_class_list",
kwargs={"username": request.user.username}))
else:
form = WeeklyClassForm()
return render(
request,
"weekly_classes/new_weekly_class.html",
{"form": form})
form.save() does not directly save the many to many instances so after saving the form.save() also call form.save_m2m() to save manytomay relationship.
#login_required
def new_weekly_class(request):
if request.method == "POST":
form = WeeklyClassForm(request.POST, request.FILES)
if form.is_valid():
form.save()
form.save_m2m()
return redirect("weekly_class_list", kwargs={"username": request.user.username})
else:
form = WeeklyClassForm()
template = "weekly_classes/new_weekly_class.html"
context = {"form": form}
return render(request, template, context)
for reference: https://docs.djangoproject.com/en/dev/topics/forms/modelforms/#the-save-method
Related
I need to assign posts to user in Django. I created
user = models.ForeignKey('authentication.CustomUser', on_delete=models.CASCADE)
and then if I display this model in my form.html I have to choice one of all users, if I don't display user in my form.html the form's isn't save my views file :
def formularz(request):
form = DodajForm(request.POST)
if form.is_valid():
ogloszenie = form.save(commit=False)
ogloszenie.user = request.user
ogloszenie.save()
return redirect('atrakcje:after')
else:
ogloszenie = DodajForm()
context = {
'form': form,}
return render(request, 'formularz.html', context)
Can i please know how to resolve it?
Rewrite the form to exclude the user field:
class DodajForm(forms.ModelForm):
class Meta:
model = Dodaj
exclude = ['user']
In the view, you better alter the instance, and let the form do the save logic, since a ModelForm can also save many-to-many fields:
def formularz(request):
if request.method == 'POST':
form = DodajForm(request.POST, request.FILES)
if form.is_valid():
form.instance.user = request.user
form.save()
return redirect('atrakcje:after')
else:
ogloszenie = DodajForm()
context = {'form': form}
return render(request, 'formularz.html', context)
Trying to setup simple formset, but getting errors
'SWDataForm' object has no attribute 'save'
MODEL
class SWData(ValidateOnSaveMixin, models.Model):
model_serial = models.ForeignKey(SWInfo, related_name='serial_items', on_delete=models.SET_NULL, null=True)
hostname = models.CharField(max_length=20, default='', unique=True)
deployed = models.BooleanField()
class Meta:
verbose_name_plural = "SWDATA"
def __str__(self):
return "{0}".format(self.hostname)
VIEW
def display_data(request, data, **kwargs):
return render(request, 'web/posted-data.html', dict(data=data, **kwargs))
def swdata(request, *args, **kwargs):
template = 'web/swdata.html'
SWDataFormset = modelformset_factory(SWData, fields=('__all__'), extra=1)
formset = SWDataFormset(request.POST or None)
if request.method == 'POST':
print(formset.is_valid())
if formset.is_valid():
pprint(formset)
for form in formset.forms:
if form.is_valid():
try:
if form.cleaned_data.get('DELETE') and form.instance.pk:
form.instance.delete()
else:
instance = form.save(commit=False)
#instance.model_serial = model_serial
#print (instance.model_serial)
instance.save()
messages.success(request, "Successfully")
except formset.DoesNotExist:
messages.error(request, "Database error. Please try again")
#data = form.cleaned_data
#return display_data(request, data)
else:
formset = SWDataFormset(request.POST or None)
return render(request, template, {'formset': formset})
Remove the form and used the modelformset_factory, I was able to save only the last entry in the formset. How to loop thru each formset prefix and save each item?
I think that you are trying to save data form from a form, and you can't do that. You need to use the method create of your model. Something like SWDData.objects.create(form).
remove this line
instance = form.save(commit=False)
this line is needed when you wants to edit the form.
Now check if it works
Even I also got similar Problem
This question already has answers here:
Saving Many To Many data via a modelform in Django
(2 answers)
Closed 4 years ago.
There's form with many fields (Date, Char, Text, Image, URL...) and they works fine. I mean values are submitted to DB as they must. But when I added ManyToManyField, it didn't save the value of this MultipleChoice form to DB. Any ideas why?
models.py:
class EventTag(models.Model):
tags = models.CharField(max_length=300)
def __str__(self):
return self.tags
class Article(models.Model):
source = models.CharField(max_length=100)
source_img = models.ImageField(default='default.png', blank=True)
#other fields
event_tags = models.ManyToManyField(EventTag, blank=True)
forms.py:
class CreateArticle(forms.ModelForm):
class Meta:
model = models.Article
fields = ['source', 'source_img', 'event_tags', ]
views.py:
def article_create(request):
if request.method == 'POST':
form = forms.CreateArticle(request.POST, request.FILES)
if form.is_valid():
instance = form.save(commit=False)
instance.author = request.user
instance.save()
return redirect('articles:list')
else:
form = forms.CreateArticle()
return render(request, 'articles/article_create.html', { 'form': form })
after save your form you must call form.save_m2m(). your view must be like this:
def article_create(request):
if request.method == 'POST':
form = forms.CreateArticle(request.POST, request.FILES)
if form.is_valid():
instance = form.save(commit=False)
instance.author = request.user
instance.save()
form.save_m2m()
return redirect('articles:list')
else:
form = forms.CreateArticle()
return render(request, 'articles/article_create.html', { 'form': form })
I need to add multiple images in django form to one model. I did a research and for form outside of django I try to setup django-multiupload.
My models.py:
class Profile(models.Model):
...
...
first = models.ImageField("first", upload_to='first')
second = models.ImageField("second", upload_to='second')
...
In forms.py:
class AddForm(forms.ModelForm):
first = MultiImageField(min_num=1, max_num=20)
second = MultiImageField(min_num=1, max_num=4)
In views.py:
class UploadView(FormView):
template_name = 'finder/submit.html'
form_class = AddForm
success_url = '/'
def form_valid(self, form):
for each in form.cleaned_data['first']:
Profile.objects.create(first=each)
for each in form.cleaned_data['second']:
Profile.objects.create(second=each)
return super(UploadView, self).form_valid(form)
And on submitting form this form creates multiple Profile objects with only first/second field filled.
How can I create only one model with remaining fields (other than first/second) and with multiple first/second fields?
It was my function-based view before adding multiupload but I couldn't make it work, maybe it's easier to change it somehow?
def add_form(request, *args, **kwargs):
if request.method == "POST":
form = AddForm(request.POST)
if form.is_valid():
profile = form.save(commit=False)
profile.save()
return redirect('/', pk=profile.pk)
else:
form = AddForm()
return render(request, 'finder/submit.html', {'form': form})
I have never used the Django-multiupload, but I happen to read some of the docs.
If you want to save multiple files for your user model, you may need to create another model for accommodating the files and add a Foreign Key towards the Profile model.
Remove the first and second fields from Profile model. It causes you to create multiple profiles with same data inorder to accomodate multiple images.
Simple example,
class Image(models.Model):
image = models.FileField()
profile = models.ForeignKey(Profile, related_name='images')
is_first = models.BooleanField(default=False)
is_second = models.BooleanField(default=False)
Then, edit the save method in form,
class AddForm(forms.ModelForm):
first = MultiImageField(min_num=1, max_num=20)
second = MultiImageField(min_num=1, max_num=4)
class Meta:
model = Profile
fields = (.......... 'first', 'second')
def save(self, commit=True):
first_images = self.cleaned_data.pop('first')
second_images = self.cleaned_data.pop('second')
instance = super(AddForm, self).save()
for each in first_images:
first = Image(image=each, profile=instance, is_first=True)
first.save()
for each in second_images:
second = Image(image=each, profile=instance, is_second=True)
second.save()
return instance
Then, on the views, edit the view,
class UploadView(FormView):
template_name = 'finder/submit.html'
form_class = AddForm
success_url = '/'
def form_valid(self, form):
instance = form.save(commit=False)
instance.user = self.request.user
instance.save()
return super(UploadView, self).form_valid(form)
Or in function based view,
def add_form(request, *args, **kwargs):
if request.method == "POST":
form = AddForm(request.POST)
if form.is_valid():
profile = form.save(commit=False)
profile.user = request.user
profile.save()
return redirect('/', pk=profile.pk)
else:
form = AddForm()
return render(request, 'finder/submit.html', {'form': form})
I've a model (Parent model):
class Post(models.Model):
image = models.ImageField(upload_to='%Y/%m/%d')
title = models.CharField(max_length=200)
width = models.DecimalField(max_digits=3, decimal_places=0)
height = models.DecimalField(max_digits=3, decimal_places=0)
year = models.PositiveIntegerField()
def __str__(self):
return self.title
and another model (Child model):
class Addimg(models.Model):
post = models.ForeignKey('Post', null=True)
addimg = models.ImageField(upload_to='%Y/%m/%d')
def __str__(self):
return self.post
My Addimg Form:
class AddimgForm(forms.ModelForm):
class Meta:
model = Addimg
fields = ('post', 'addimg', 'width', 'height',)
views.py using the form:
def addimg(request, pk):
if request.method == "POST":
form = AddimgForm(request.POST, request.FILES)
post = get_object_or_404(Post, pk=pk)
if form.is_valid():
addimg = form.save(commit=False)
addimg.addimg = request.FILES['addimg']
addimg.save()
return redirect('blog.views.detail', pk=post.pk)
else:
form = AddimgForm()
return render(request, 'blog/edit.html', {'form': form})
And my Problem is that when I create a "Child model" my post field returns all instances of allready created Post models as choices. What I want is that it automatic only displays the one Post it is related to without choices. Is ForeignKey the right model for that?
Any Ideas how this could work. thanks
ForeignKey field is translated into ModelChoiceField inside a Django ModelForm. If you inspect that class you will notice that this type of field has an queryset attribute required. By default Django provides the full set of objects. You can override this inside your form __init__ method by providing the parent object the form will need.
Consider the following example code:
def addimg(request, pk):
post = get_object_or_404(Post, pk=pk)
if request.method == "POST":
form = AddimgForm(request.POST, request.FILES, post=post)
#...
else:
form = AddimgForm(post=post)
return render(request, 'blog/edit.html', {'form': form})
class AddimgForm(forms.ModelForm):
class Meta:
model = Addimg
fields = ('post', 'addimg', 'width', 'height',)
def __init__(self, *args, **kwargs):
post = kwargs.pop('post')
super(AddimgForm, self ).__init__(*args, **kwargs)
self.fields['post'].queryset = Post.objects.filter(id=post.id)
What you want to do is create a Many-to-one relationship. For example,
post = models.ForeignKey('Post', null=True)
This means you can filter on it for example,
Addimg.objects.filter(post=Post)
or
Post.objects.get(pk=1)
Post.addimg_set.filter(xyz=etc)
Read more here: https://docs.djangoproject.com/en/1.8/topics/db/examples/many_to_one/