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).
Related
Straight from the Django Docs....
from django.db import models
class Place(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=80)
class Restaurant(Place):
serves_hot_dogs = models.BooleanField(default=False)
serves_pizza = models.BooleanField(default=False)
I can see the data in my database so I know it's working...but I can't figure out how to access the data. In the past I have used Class Based Views...this is my first go at using a multi-table inheritance structure beyond class based views.
I have tried to do...
def get_absolute_url(self):
return reverse("App:create_restaurant_detail",kwargs={'pk':self.object.place_ptr_id})
Here's my urls...
path("create_restaurant",views.CreateRestaurantView.as_view(), name='create_restaurant'),
path("create_restaurant_detail/<pk>/",views.CreateRestaurantDetailView.as_view(), name='create_restaurant_detail'),
And my Views....
class CreateRestaurantView(LoginRequiredMixin,CreateView):
model = Restaurant
form_class = CreateRestaurantForm
template_name = 'create_restaurant.html'
def get_success_url(self):
return redirect('App:create_restaurant_detail', kwargs={'pk':self.object.place_ptr_id})
class CreateRestaurantDetailView(LoginRequiredMixin,DetailView):
model = Restaurant
context_object_name = 'restaurant_detail'
template_name = 'create_restaurant_detail.html'
But the url lookup keeps saying not found.
In the log...I see....
django.urls.exceptions.NoReverseMatch: Reverse for 'create_restaurant_detail' with keyword arguments '{'kwargs': {'pk': 12}}' not found. 1 pattern(s
) tried: ['LevelSet/App/create_restaurant_detail/1bad5cba\\-a087\\-4f0a\\-9c3b\\-65ac096c3e42/(?P<pk>[^/]+)/$']
I'm trying to figure out how to access the data in the table. Thanks for any thoughts and help in advance.
class CreateRestaurantView(LoginRequiredMixin,CreateView):
model = Restaurant
form_class = CreateRestaurantForm
template_name = 'create_restaurant.html'
def form_valid(self, form):
instance = form.save()
return JsonResponse({ 'id': instance.pk, 'success_url': self.get_success_url() })
def post(self, request, *args, **kwargs):
if "cancel" in request.POST:
return HttpResponseRedirect(reverse('Main:main'))
else:
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
restaurant_instance = form.save()
self.object = restaurant_instance
return self.form_valid(form)
def get_success_url(self):
return reverse('Newapp:create_restaurant_detail', kwargs={ 'pk' : self.object.place_ptr_id })
My URL...
path("create_restaurant_detail/<int:pk>/",views.CreateRestaurantDetailView.as_view(), name='create_restaurant_detail'),
I misunderstood CreateView. This code is working at the moment, but I'm not sure JsonResponse is the best answer for form_valid. I couldn't figure out the proper alternative but my original problem has been solved. Thanks to Iain for helping me work through it.
Why am I getting this error?
TypeError: super(type, obj): obj must be an instance or subtype of type
This is my models.py file
class UserNotification(models.Model):
Name = models.CharField(max_length=250)
Mobile_No = models.CharField(max_length=10, validators=[RegexValidator(r'^\d{1,10}$')])
Proof = models.TextField()
viewed = models.BooleanField(default=False)
user = models.ForeignKey(User)
date = models.DateTimeField(default=timezone.now)
def __str__(self):
return self.Name
class Meta:
ordering = ["-date"]
This is my views.py file
class RequestItem(generic.CreateView):
model = UserNotification
fields = ['Name', 'Mobile_No', 'Proof']
def get_form(self, form_class=None):
if form_class is None:
form_class = self.get_form_class()
form = super(UserNotification, self).get_form(form_class)
form.fields['Name'].widget = TextInput(attrs={'placeholder': '*Enter your name'})
form.fields['Mobile_No'].widget = TextInput(
attrs={'placeholder': "*Enter your's mobile number to get a call back from angel"})
form.fields['Proof'].widget = TextInput(attrs={'placeholder': '*enter proof you have for your lost item'})
return form
def form_valid(self, form):
print(self.kwargs)
self.object = form.save(commit=False)
qs = Report_item.objects.filter(id=self.kwargs.get("pk"))
self.object.user = qs[0].owner
self.object.save()
return HttpResponse("<h1>Your request has been processed</h1>")
I am using django 1.11. There was no error and code working properly until I add the placeholder function. After adding the placeholder I am getting this error. Please help me to resolve it.
The problem is where you call super() inside get_form. You need to use the current class; for some reason you have put the model class there. It needs to be:
form = super(RequestItem, self).get_form(form_class)
Or better, since you are using Python 3, use the short version:
form = super().get_form(form_class)
Note however this isn't really a good way to do what you're trying to do here. Rather, declare an actual form class which sets the widget attributes for the fields you want to change, and refer to it in the view class by setting the form_class attribute at class level.
I have two modelforms--one includes a standard ImageField and the other is an inlineformset of ImageFields.
The normal standard ImageField renders on the page with the option for "Clear[ing]" while the inlineformset ImageFields render with an optional "Delete" tickbox.
Either Clear or Delete will remove the image from the User's profile, but the actual image file will remain in storage, as well as the URL to the image.
I am trying to remove all associations to the image once the User updates his Profile form by "clearing" or "deleting" the image.
I found FieldFile.delete which I believe just requires me to call .delete() on the instance, but I'm not sure how to conditionally check whether the user is updating the form with the "delete" box or "clear" box ticked.
Here are the two models containing the image fields:
class Profile(models.Model):
profile_photo = models.ImageField(
upload_to=profile_photo_upload_loc,
null=True,
blank=True,
verbose_name='Profile Image',
)
class CredentialImage(models.Model):
profile = models.ForeignKey(Profile, default=None,
related_name='credentialimage')
image = models.ImageField(
upload_to=credential_photo_upload_loc,
null=True,
verbose_name='Image Credentials',
)
The ModelForms:
class ProfileUpdateForm(ModelForm):
class Meta:
model = Profile
fields = [
"profile_photo",
]
class CredentialImageForm(ModelForm):
image = ImageField(required=False, widget=FileInput)
class Meta:
model = CredentialImage
fields = ['image', ]
CredentialImageFormSet = inlineformset_factory(Profile,
CredentialImage, fields=('image', ), extra=2)
The view:
class ProfileUpdateView(LoginRequiredMixin, UpdateView):
form_class = ProfileUpdateForm
def get_context_data(self, **kwargs):
data = super(ProfileUpdateView, self).get_context_data(**kwargs)
if self.request.POST:
data['credential_image'] = CredentialImageFormSet(self.request.POST, self.request.FILES, instance=self.object)
else:
data['credential_image'] = CredentialImageFormSet(instance=self.object)
return data
def get_object(self, *args, **kwargs):
user_profile = self.kwargs.get('username')
obj = get_object_or_404(Profile, user__username=user_profile)
return obj
def form_valid(self, form):
data = self.get_context_data()
formset = data['credential_image']
if formset.is_valid():
self.object = form.save()
formset.instance = self.object
formset.save()
return redirect(self.object.get_absolute_url())
else:
print("WTF")
instance = form.save(commit=False)
instance.user = self.request.user
return super(ProfileUpdateView, self).form_valid(form)
My View is all kinds of messed up but it seems to work. Now looking to completely remove the image files from storage upon update.
Use Django-cleanup:
pip install django-cleanup
settings.py
INSTALLED_APPS = (
...
'django_cleanup', # should go after your apps
...
)
Django 1.10
I'm trying to add data to a form programmatically.
class Wiki(models.Model):
related_model = models.CharField(max_length=100, blank=False, null=False, default="")
related_object_id = models.CharField(max_length=100, blank=False, null=False, default="")
article = models.TextField(blank=False, null=False, default="")
class WikiCreate(CreateView):
model = Wiki
fields = ['article']
def post(self, request, *args, **kwargs):
related_model = kwargs.get('model')
related_object_id = kwargs.get('pk')
form = self.get_form()
form.data._mutable = True
form.data['related_model'] = related_model
form.data['related_object_id'] = related_object_id
form.data._mutable = False
return super(WikiCreate, self).post(request, *args, **kwargs)
In the post method of the superclass I place a breakpoint:
class ProcessFormView(View):
def post(self, request, *args, **kwargs):
"""
Handles POST requests, instantiating a form instance with the passed
POST variables and then checked for validity.
"""
form = self.get_form()
if form.is_valid(): # breakpoint
return self.form_valid(form)
else:
return self.form_invalid(form)
Well, what can I see at the breakpoint.
form.data =
Then step in the debugger. form.is_valid() returns true. So, now I can see that: 1) _errors is empty; 2) cleaned_data = {'article': "Some text I've just input."}.
Well, 'related_model' and 'related_object_id' have not appeared in the cleaned data.
Could you help me understand why data from these fields are not saved?
This is because you only have the fields:
fields = ['article']
So there are no other fields on your form other than article. Try adding the other two fields to the fields array. If you want them to be there, but not visible you need to create a custom form and set them to have the hidden widget
This isn't the way to add data to a form submission. You should be adding it to the model instance, not the form, once that has been created in the form_valid method. You shouldn't be overriding post at all.
class WikiCreate(CreateView):
model = Wiki
fields = ['article']
def form_valid(self, form):
related_model = self.kwargs.get('model')
related_object_id = self.kwargs.get('pk')
item = form.save(commit=False)
item.related_model = related_model
item.object_id = related_object_id
item.save()
return redirect(self.get_success_url())
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)