how to save many to many fields in django create view - django

Here is my models:
class Category(models.Model):
name = models.CharField(max_length=255)
slug = models.SlugField(unique=True, max_length=255, blank=True,default=None)
desc = models.TextField(blank=True, null=True )
.....
class Post(models.Model):
title = models.CharField(max_length=255)
content = models.TextField()
categories = models.ManyToManyField(Category, blank=True, through='CatToPost')
.......
class CatToPost(models.Model):
post = models.ForeignKey(Post)
category = models.ForeignKey(Category)
The problem now I can't make it work to save the many-to-many field by using the generic create view.
Cannot set values on a ManyToManyField which specifies an intermediary
model. Use posts.CatToPost's Manager instead.
In SO there was a similar problem that suggest override the form_valid method to manually create the relation, but it didn't works for me.
def form_valid(self, form):
self.object = form.save(commit=False)
for cat in form.cleaned_data['categories']:
cate = CatToPost()
cate.post = self.object
cate.category = cat
cate.save()
return super(AddStoryForm, self).form_valid(form)
The error:
Cannot assign "": "Post" instance isn't saved
in the database.
Seem self.object = form.save(commit=False) not saving in the db, so the Post ID wasn't created.
But when I turn self.object = form.save(commit=True) , I still got the previous error occurred again.
Any idea how can I overcome this problem?

I also had a similar problem to the answer you listed. For me what worked is to add self.object.save() after self.object = form.save(commit=False)
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.save()
for cat in form.cleaned_data['categories']:
cate = CatToPost()
cate.post = self.object
cate.category = cat
cate.save()
return super(AddStoryForm, self).form_valid(form)

Related

Django adding link to parent when creating child in a many to one relationship

I have two models in a one to many relationship.
I am editing a parent record and creating a child.
When I create the child I cannot figure out how the send a reference of the parent so that I can instantiate the ForiegnKey of the child to point to the parent.
Could anyone help.
thanks
The parent is:
class Site(models.Model):
name = models.CharField(max_length=100)
address1 = models.CharField(max_length=100)
address2 = models.CharField(max_length=100)
postcode = models.CharField(max_length=50)
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="sites")
def __str__(self):
return f"{self.name}, {self.postcode}"
def get_queryset():
return set.request.user.sites.all()
the child is:
class Administrator(models.Model):
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
telephone = models.CharField(max_length=50)
email = models.EmailField()
site = models.ForeignKey(
Site, on_delete=models.CASCADE, related_name="adminstrators"
)
def __str__(self):
return f"{self.first_name} {self.last_name}, {self.email}"
I am trying to point the child at the parent in the child's validation function:
def form_valid(self, form):
self.object = form.save(commit=False)
self.site = # I don't know what to put here as I have to reference to the parent Site object
self.object.save()
return HttpResponseRedirect(self.get_success_url())
Method 1: Create a view for Site instance, that will show a particular site by id. For example: /site/1/. In that view, instantiate a form for Administrator. Then in the form_valid method, you can pass the site as self.object like this:
def form_valid(self,form):
form.instance.site = self.get_object()
return super().form_valid(form)
Method 2: Create a form for Administrator instance. Then, site will be a dropdown, where you can select a site and submit the form.
Choose whatever suits you best.
I finally did it this way.
The url.py had the following line:
path('administrator/new/<int:site_id>',views.AdministratorCreateView.as_view(),name='administrator.new'),
The html linked to my detail view with:
Create
and my view class is as follows:
class AdministratorCreateView(CreateView):
model = Administrator
form_class = AdministratorForm
template_name = "register/administrator_new.html"
def get_success_url(self):
return reverse_lazy("administrator.detail", kwargs={"pk": self.object.pk})
def form_valid(self, form):
site_id = self.kwargs['site_id']
self.object = form.save(commit=False)
self.object.site = Site.objects.get(pk=site_id)
self.object.save()
return HttpResponseRedirect(self.get_success_url())
This does what i was trying to do, thanks to all for the help.

Django CreateView- ModelForm with many-to-many InlineFormset ValueError

I'm struggling with such a problem:
I have models:
class ForecastType(models.Model):
client = models.ForeignKey(Client, related_name="weatherforecast_client")
created_by = models.ForeignKey(Forecaster, related_name="weatherforecast_created_by")
modified_by = models.ForeignKey(Forecaster,
related_name="weatherforecast_modified_by",
blank=True,
null=True)
creation_date = models.DateTimeField(auto_now_add=True)
modification_date = models.DateTimeField(auto_now=True)
weather_forecasts = models.ManyToManyField('WeatherForecast')
STATUS_CHOICES = (
("D", "Draft"),
("A", "Active"),
("H", "History"),
)
status = models.CharField(max_length=1, choices=STATUS_CHOICES, default="D")
class OneDayForecast(ForecastType):
def __str__(self):
return f"Prognoza pogody dla: {self.client}, wykonana dnia: {self.creation_date}"
class WeatherForecast(models.Model):
begin_date = models.DateTimeField(blank=True, null=True)
finish_date = models.DateTimeField(blank=True, null=True)
forecast_type = models.ForeignKey('ForecastType', null=True, blank=True)
description = models.TextField(max_length=300, blank=True, null=True)
I also have ModelForm and InlineFormset:
class OneDayForecastForm(ModelForm):
class Meta:
model = OneDayForecast
exclude = ('weather_forecasts',)
WeatherForecastFormset = inlineformset_factory(OneDayForecast, WeatherForecast, exclude=('forecast_type',), extra=2)
and finally an CreateView:
class OneDayForecast(ForecasterRequiredMixin, CreateView):
template_name = "forecaster/one_day.html"
success_url = reverse_lazy("forecaster:dashboard")
model = OneDayForecast
form_class = OneDayForecastForm
def get(self, request, *args, **kwargs):
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
weather_forecast_form = WeatherForecastFormset()
return self.render_to_response(
self.get_context_data(form=form, weather_forecast_form=weather_forecast_form)
)
def post(self, request, *args, **kwargs):
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
weather_forecast_form = WeatherForecastFormset(self.request.POST)
if form.is_valid() and weather_forecast_form.is_valid():
return self.form_valid(form, weather_forecast_form)
else:
return self.form_invalid(form, weather_forecast_form)
def form_valid(self, form, weather_forecast_form):
self.object = form.save(commit=False)
for weather_form in weather_forecast_form:
weather_object = weather_form.save()
self.object.weatherforecast_set.add(weather_object)
self.object.save()
form.save_m2m()
return HttpResponseRedirect(self.get_success_url())
def form_invalid(self, form, weather_forecast_form):
return self.render_to_response(
self.get_context_data(form=form, weather_forecast_form=weather_forecast_form)
)
After trying to submit my Form with it's InlineFormset I receive this error:
Request Method: POST
Request URL: http://localhost:8000/forecaster/my-clients/6/one_day/
Django Version: 1.11
Exception Type: ValueError
Exception Value:
Unsaved model instance <OneDayForecast: Forecast for: client1> cannot be used in an ORM query.
Problem probably lies in commit=False in form_valid method but I have no clue how to repair it.
Does anyone know to solve this?
Thanks.
Okay, so I think that there are a couple problems here, both in your post and form_valid() methods. I've referred to my own implementations of inline formsets to see what you do differently.
First of all, I believe that the first line of the post method should be self.object = self.get_object().
Second, weather_forecast_form = WeatherForecastFormset(self.request.POST) should be weather_forecast_form = WeatherForecastFormset(self.request.POST, instance=self.object).
Notice the relationship here between the object we get and then using it at the instance in the formset. That's all for the post method.
Now, in my own implementation, I have many formsets, so I loop through each formset as follows (you can use exactly the same code if you put your formset into a list and pass it to form_valid):
def form_valid(self, form, formsets):
self.object = form.save()
for formset in formsets:
formset.instance = self.object
formset.save()
return HttpResponseRedirect(self.get_success_url())
Notice that we fully save the parent form here, including committing it. We then save all formsets. If you wanted to keep your single formset, you can easily change the above code to the following:
def form_valid(self, form, weather_forecast_form):
self.object = form.save()
weather_forecast_form.instance = self.object
weather_forecast_form.save()
return HttpResponseRedirect(self.get_success_url())
The error that you report at the bottom of your question is a direct result of form.save(commit=False). What is happening there is that you are "pretend" saving the parent, and then trying to fully save the children. The database doesn't have record of the parent, so it spits out that error. Committing before saving many to many records is a must (at least in my experience).

Generic detail view needs object pk or a slug

But I already refer to primary keys, don't I?
It says this error relates to:
class CommentUpdate(UpdateView):
model = Comment
fields = ['body']
def form_valid(self, form):
film = Film.objects.get(pk=self.kwargs['film_id'])
comment = Film.objects.get(pk=self.kwargs['comment_id'])
form.instance.user = self.request.user
form.instance.film = film
form.instance.comment = comment
return super(CommentUpdate, self).form_valid(form)
I am not sure once this issue is fixed if that code above will work but the view I have to create a comment does:
class CommentCreate(CreateView):
model = Comment
fields = ['body']
def form_valid(self, form):
film = Film.objects.get(pk=self.kwargs['film_id'])
form.instance.user = self.request.user
form.instance.film = film
return super(CommentCreate, self).form_valid(form)
My urls.py:
path('<int:film_id>/comment/', views.CommentCreate.as_view(), name='add_comment'),
path('<int:film_id>/comment/<int:comment_id>/', views.CommentUpdate.as_view(), name='update_comment'),
model:
class Comment(models.Model):
# user = models.ForeignKey(User, on_delete=models.CASCADE)
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
film = models.ForeignKey(Film, on_delete=models.CASCADE)
body = models.CharField(max_length=200)
def get_absolute_url(self):
return reverse('films:detail', kwargs={'pk': self.film.pk})
And html link I have:
Leave a comment
Update
UpdateView calling get_object method which required pk or slug url argument to get updating object. You can change name of argument with pk_url_kwarg:
class CommentUpdate(UpdateView):
model = Comment
fields = ['body']
pk_url_kwarg = 'comment_id'

getting error while relating a foreign key to save form in django

models.py
class OtherData(models.Model):
title = models.CharField(max_length=120)
user = models.ForeignKey(settings.AUTH_USER_MODEL)
class ProductImage(models.Model):
otherdata = models.ForeignKey(OtherData)
user = models.ForeignKey(settings.AUTH_USER_MODEL)
image = models.FileField(blank=True, null=True, upload_to='images/')
I am looking for saving an image on an instance of otherdata, getting integrity error NOT NULL constraint failed. I am using a model form to save data. I tried to use form valid method as follows in views.py but still the same error.
def form_valid(self, form):
instance = form.save(commit=False)
instance.user = self.request.user
instance.otherdata_id = self.kwargs.get('pk')
return super(ImageCreateView, self).form_valid(form)
Looking forward for a help, thank you.
can you try this,you can get OtherData instance from pk
def form_valid(self, form):
instance = form.save(commit=False)
instance.user = self.request.user
instance.otherdata = OtherData.objects.get(pk=self.kwargs.get('pk'))
return super(ImageCreateView, self).form_valid(form)
and also small changes in model.py
class OtherData(models.Model):
title = models.CharField(max_length=120)
user = models.ForeignKey(settings.AUTH_USER_MODEL,related_name='other_data')
class ProductImage(models.Model):
otherdata = models.ForeignKey(OtherData)
user = models.ForeignKey(settings.AUTH_USER_MODEL,related_name='product_image')
image = models.FileField(blank=True, null=True, upload_to='images/')

Need help getting correct instance for form_valid in a generic view

I can't work out how to get the correct instance for the form_valid part of my generic view.
I am trying to allow a user to post on their project wall(bit like Facebook). I need the post to be related to an individual project(a user can have more than one project). Should the instance be a pk or the project title? Any example code or help would be very appreciated! I struggle understanding how when you create a new post, it knows which project to associate itself with.
views
class NewPost(CreateView):
model = ProjectPost
form_class = ProjectPostForm
template_name = 'howdidu/new_post.html'
def form_valid(self, form):
newpost = form.save(commit=False)
form.instance.user = self.request.user
newpost.save()
self.object = newpost
return super(NewPost, self).form_valid(form)
def get_success_url(self):
project_username = self.request.user.username
project_slug = self.object.slug
return reverse('user_project', kwargs={'username':project_username, 'slug': project_slug})
models
class UserProject(models.Model):
user = models.ForeignKey(User)
title = models.CharField(max_length=100)
project_overview = models.CharField(max_length=1000)
project_picture = models.ImageField(upload_to='project_images', blank=True)
date_created = models.DateTimeField(auto_now_add=True)
project_views = models.IntegerField(default=0)
project_likes = models.IntegerField(default=0)
project_followers = models.IntegerField(default=0)
slug = models.SlugField(max_length=100, unique=True) #should this be unique or not?
def save(self, *args, **kwargs):
self.slug = slugify(self.title)
super(UserProject, self).save(*args, **kwargs)
def __unicode__(self):
return self.title
class ProjectPost(models.Model):
project = models.ForeignKey(UserProject)
title = models.CharField(max_length=100)
post_overview = models.CharField(max_length=1000)
date_created = models.DateTimeField(auto_now_add=True)
post_views = models.IntegerField(default=0)
post_likes = models.IntegerField(default=0)
forms
#form to add project details
class UserProjectForm(forms.ModelForm):
class Meta:
model = UserProject
fields = ('title', 'project_picture', 'project_overview')
#form to create a post
class ProjectPostForm(forms.ModelForm):
class Meta:
model = ProjectPost
fields = ('title', 'post_overview')
Ok, in that case, I would recommend a URL something like
url(r'^(?P<pk>\d+)/post/add/$', views.NewPostCreateView.as_view(), name='...'),
and then a view like
class NewPost(CreateView):
model = ProjectPost
form_class = ProjectPostForm
template_name = 'howdidu/new_post.html'
def form_valid(self, form):
self.object = form.save(commit=False)
# Find project by using the 'pk' in the URL
project = get_object_or_404(UserProject, pk=self.kwargs['pk'])
# Then just set the project on the newPost and save()
self.object.project = project
self.object.save()
return super(NewPost, self).form_valid(form)
def get_success_url(self):
# Unchanged ...
I see in your code that you were trying to do something with the user but I don't understand why your Post does not have a user field (you may want to add a created_by) and the UserProject should already have a user set.
I am also assuming the user got to the his/her project first, so you know by definition that the project he is adding a post to is his. If that is not the case, then just change the logic to get the UserProject through a regular query. e.g. maybe with `UserProject.objects.get(user = self.request.user) if there is one project per user (again, just as an example).
Anyway, I am making some assumptions here, but hopefully the main question was how to set the project on the newPost and that is answered in my example.