So I have a modelformset for a House model, which has an owners manytomany field, I'm excluding the owners field due to the fact that I want it to just automatically save the currently logged in users id as the owner - I could hack this with hidden fields, but would rather know how it's properly done.
For clarity, the Integrity Error is that owner_id can not be null, my attempt at fixing it by hardcoding just to see failed in the beginning of manage_houses
views.py
def manage_houses(request):
HousesFormSet = modelformset_factory(House, form=ManageHousesForm)
if request.method == 'POST':
formset = HousesFormSet(request.POST)
# failed attempt at fixing integrity error
for form in formset:
form.owner_id = 1
if formset.is_valid():
if formset.save():
notice = "Success! Your houses were updated in the system."
notice_type = "success"
elif not formset.has_changed():
pass
else:
notice = "Something went wrong! Your houses may not have been updated."
notice_type = "error"
else:
formset = SpecialsFormSet()
response_details = { 'formset': formset,
'fields': ManageHousesForm.base_fields }
try:
response_details['notice'] = notice
response_details['notice_type'] = notice_type
except NameError:
pass
return render_to_response('houses/manage.djhtml', response_details)
models.py
class House(models.Model):
class Meta:
app_label = 'houses'
# Fields
owners = models.ManyToManyField(User)
name = models.CharField(max_length=50)
slug = models.SlugField(max_length=100, blank=True, editable=False, unique=True)
address = models.CharField(max_length=75)
def __unicode__(self):
return self.name
According to the Django Documentation, this is what inline formsets are for:
Inline formsets is a small abstraction layer on top of model formsets.
These simplify the case of working with related objects via a foreign
key.
Related
Seems like model is only getting "null". I tried to print the request user in views and it was fine.
View codes.
def add_item(request): # add new media
quote = random.choice(quotes)
if request.method == "POST":
new_item = MovieForm(request.POST)
new_item.save(commit=False)
new_item.author = request.user
new_item.save()
return redirect('movie:movie_library')
else:
movie_form = MovieForm()
return render(request, 'movie/add_movie.html',
context={'form': movie_form,
'quote': quote})
model
class Movie(models.Model):
STATUS_CHOICES = (
('Movie', 'Movie'),
('Tv-series', 'Tv Series'),
('Anime', 'Anime'),
)
author = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=50, null=False)
type = models.CharField(max_length=15,
choices=STATUS_CHOICES,
default='')
year = models.IntegerField(null=True, blank=True)
rating = models.FloatField(max_length=10, null=False)
review = models.TextField(null=True, blank=True)
img_url = models.TextField(default='https://betravingknows.com/wp-content/uploads/2017/'
'06/video-movie-placeholder-image-grey.png')
active = models.BooleanField(default=True)
def __str__(self):
return self.title
forms
from django import forms
from movie.models import Movie
class MovieForm(forms.ModelForm):
class Meta:
model = Movie
fields = ('title', 'type', 'year', 'review',
'rating', 'img_url', 'active')
error
django.db.utils.IntegrityError: null value in column "author_id" of relation "movie_movie" violates not-null constraint
DETAIL: Failing row contains (46, Sapiens, Movie, 435, 6.5, ftght, https://betravingknows.com/wp-content/uploads/2017/06/video-movi..., t, null).
Isn't request.user return AnonymousUser instead of registered one? In this case author will get None that will raise your error.
You can add login_required decorator to your view in order to avoid this:
from django.contrib.auth.decorators import login_required
#login_required
def add_item(request):
...
In fact, in your code you're not assigning the request.user to your new object, but to your form, thus it's not saving.
form.save() returns the new object and that's what you need to manipulate here.
Also, consider handling the case where the form is not valid with form.is_valid().
if request.method == "POST":
form = MovieForm(request.POST)
if form.is_valid():
new_item = form.save(commit=False)
new_item.author = request.user
new_item.save()
Hope that helped.
Just in case. If you are using Django rest framework, and you have your serializer set up as follows:
author is foreign key. If you POST request is attempting to create a db object of model with a foreign key, then make sure it is not mentioned as read-only.
class PostSerializer(serializers.ModelSerializer):
author = serializers.ReadOnlyField(source='author.username') # just comment this
# author = UserSerializer(read_only=True) # remove this also if present
...
Please remove the readonly part. This stops the expected result to occur. Also make sure that, the author field receives the id of the type of foreingKey (here User).
I have Django app using django-taggit module. My work flow process is to add a photo, then come back to photo to assign tags using a photo_edit view, photo_edit form and photo_edit.html template as shown below.
The app successfully tags the photo, using the 'tags': TagWidget() in the form and template. This works fine, and it can save new, modified or deleted tags against that photo.
But I want to be able to record the user id of person adding tag in the django taggit model database table. django taggit doesn't have this feature.
To get started, I have successfully added a new user field to the class TagBase(models.Model): using settings.AUTH_USER_MODEL to refer to user object which creates new user_id column in the taggit_tags table when makemigrations is run.
Now I need to figure out how to get current user id to populate that new user_id column when adding or editing tagged object tags.
I have tried doing this in both my app view and django taggit model. Ideally, since this would be a default feature, the user id is saved in django taggit model.
In django taggit model I have tried adding self.user = get_user_model().objects.get(id=request.user.id) to the TagBase def save function. But of course it doesn't know what request is. I also tried creating new function in the TagBase model class as below, and calling it from the def save function but it returns nothing.
def get_user(request):
user = request.user
print('MODEL request user', request.user)
return user
As a test, I replaced request.user.id with hard coded id eg self.user = get_user_model().objects.get(id=1) which actually does write 1 to the taggit_tag table user_id field for that tag.
I tried doing this in the app view too. For example, I have tried photo.tag.user_id = request.user.id before save_m2m() which didn't work. It is not clear what syntax, if any, is available to save related model fields. I believe I would have to modify the form TagWidget() to include user_id field so save_2m2() would save it, but want to avoid modifying django-taggit too much.
I have searched exhaustively and have not found anything specific to Django and django-taggit. Perhaps someone could help from Python perspective!
Photo edit form
class PhotoEditForm(ModelForm):
#filename = forms.ImageField()
class Meta:
model = Photo
fields = (
'tags',
)
widgets = {
'tags': TagWidget(),
}
Photo edit view - ideally i could save request.user before form.save_m2m() something like photo.taggit_tag.user_id = user.id. Is there some syntax that would work here?
def photo_edit(request, photo_id):
photo = Photo.objects.get(id=int(photo_id))
user = get_user_model().objects.get(id=request.user.id)
photo_tags = Tag.objects.filter(photo=photo.id).order_by('name')
if request.method == "POST":
form = PhotoEditForm(request.POST, instance=photo)
if form.is_valid():
photo = form.save(commit=False)
photo.modified = timezone.now()
photo = form.save()
#save tags if any
form.save_m2m()
return HttpResponseRedirect(reverse('photo', kwargs={ 'photo_id': photo.id,}))
else:
form = PhotoEditForm(instance=photo)
context = {
'photo_form': form,
'photo': photo,
'photo_tags': photo_tags,
'title': 'Edit Mode',
}
return render(
request,
'photo_edit.html',
context,
)
Photo model
class Photo(models.Model):
filename = CloudinaryField('image')
user = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, on_delete=True)
inactive = models.BooleanField(default=False, choices=INACTIVE_CHOICES)
created = models.DateTimeField(blank=True, null=True)
modified = models.DateTimeField(blank=True, null=True)
tags = TaggableManager()
class Meta:
managed = True
db_table = 'photo'
verbose_name_plural = 'photos'
def __str__(self):
return str(self.filename)
I did make slight modification of django taggit model to include user_id field. Ideally i could set this user_id to request.user as default value but haven't seen anything yet that looks simple or easy.
class TagBase(models.Model):
name = models.CharField(verbose_name=_("Name"), unique=True, max_length=100)
slug = models.SlugField(verbose_name=_("Slug"), unique=True, max_length=100)
# added user to model which created `user_id` column on `taggit_tags` table
user = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, on_delete=True)
def __str__(self):
return self.name
def __gt__(self, other):
return self.name.lower() > other.name.lower()
def __lt__(self, other):
return self.name.lower() < other.name.lower()
class Meta:
abstract = True
def save(self, *args, **kwargs):
self.user = get_user_model().objects.get(id=1)
if self._state.adding and not self.slug:
self.slug = self.slugify(self.name)
using = kwargs.get("using") or router.db_for_write(
type(self), instance=self
)
You may want to look at django-crum: Django-CRUM (Current Request User Middleware) captures the current request and user in thread local storage.
I used it when tackling a similar issue. It allows you to access the current user without the request object having to be passed directly:
https://django-crum.readthedocs.io/en/stable/
I've read every "InterityError" + "may no be NULL" post and still can't track down what's causing this error.
I've got a two-part signup form. First part is just selecting a product. That passes a product ID to the next page as part of the URL, where they input personal info. I can get the form to work fine until I start removing fields -- i'm using model forms -- because some fields don't need to be displayed.
Here's my model, and the modelForm:
class SimpleSubscriber(models.Model):
name = models.CharField(max_length=255)
address = models.CharField(max_length=200)
city = models.CharField(max_length=100)
state = models.CharField(max_length=2)
zipcode = models.CharField(max_length=9)
phone = models.CharField(max_length=10)
email = models.EmailField()
date_created = models.DateTimeField(null=True)
sub_type = models.ForeignKey(Product)
def __unicode__(self):
return self.name
class SubscriberForm(ModelForm):
class Meta:
model = SimpleSubscriber
fields = ('name', 'address', 'city', 'state', 'zipcode', 'phone', 'email', 'sub_type',)#'date_created',
And here's my views:
def select_product(request):
title = "get yourself an e-edition. wurd."
pform = Product.objects.order_by('product_active')
if request.method == 'POST': # If the form has been submitted...
pform = ProductForm(request.POST) # A form bound to the POST data
if pform.is_valid(): # All validation rules pass
# ...
return HttpResponseRedirect('signup/%i' % pform.id) # Redirect after POST
else:
form = ProductForm() # An unbound form
return render_to_response('signup/index.html', {'title': title, 'pform': pform}, context_instance=RequestContext(request))
def subscriber_signup(request, product_id):
productchoice = Product.objects.get(id=product_id)
now = datetime.datetime.now()
title = "We need some information."
if request.method == 'POST': # If the form has been submitted...
sform = SubscriberForm(request.POST) # A form bound to the POST data
if sform.is_valid(): # All validation rules pass
sform.date_created = now
sform.sub_type = productchoice
sform.save()
return HttpResponseRedirect('thankyou/') # Redirect after POST
else:
sform = SubscriberForm() # An unbound form
return render_to_response('signup/detail.html', {'title': title, 'sform': sform, 'productchoice': productchoice, 'now': now.date(),}, context_instance=RequestContext(request))
I think it has something to do with the modelForm, but I'm pretty new, so I really have no idea. If I add all the fields to SubscriberForm, then they get filled out and everything works fine. But I don't want users to have to say when they filled out the form, so i put sform.date_created = now and I want the product_id to be filled in automatically by what choice they picked on the previous page. but if I exclude these fields from the form it throws the IntegrityError, which isn't very helpful in explaining what to change.
Any hints on where I'm messing up?
Thanks,
Two things:
1) You may benefit from using exlude in your form definition:
class SubscriberForm(ModelForm):
class Meta:
model = SimpleSubscriber
exclude = ('date_created', )
2) To your question, heres how to fix it:
if sform.is_valid(): # All validation rules pass
suscriber = sform.save(commit=False)
suscriber.date_created = now
suscriber.sub_type = productchoice
suscriber.save()
Alternatively to #fceruti's suggestion, you can also add more kwarg tags null=True on the model's field where appropriate - only forcing a minimal set of fields to be completed in the form.
I have a sample form:
class AdminDiscountForm(ModelForm):
class Meta:
model = Discount
exclude = ('company',)
the model it's pointing to is:
class Discount(models.Model):
class Meta:
verbose_name=_('Discount')
verbose_name_plural=_('Discounts')
unique_together = ('company','type')
company = models.ForeignKey(Company)
type = models.CharField(max_length=5, choices=DISCOUNT_CHOICES)
discount = models.DecimalField(max_digits=7, decimal_places=2, verbose_name=_('Discount'))
The form excludes the 'company' field because the user has already selected this using the UI.
i am planning on doing a:
company = blah
if form.is_valid():
obj = form.save(commit=False)
obj.company = company
obj.save()
The problem is that the combination of 'company' and 'type' should be unique (hence the 'unique_together'). This is enforced in the database, so django doesn't care.
I need to extend the clean() method of this form to check for uniqueness as such:
def clean(self):
cleaned_data = self.cleaned_data
# check for uniqueness of 'company' and 'type'
The problem here is that 'company' is not in there because it has been excluded.
What is the best way to raise a form validation error in this case?
-- edit
This is only for adding discount entries.
There's no initial instance.
Jammon's method is the one I use. To expand a bit (using your example):
models.py
class Discount(models.Model):
class Meta:
verbose_name=_('Discount')
verbose_name_plural=_('Discounts')
unique_together = ('company','type')
company = models.ForeignKey(Company)
type = models.CharField(max_length=5, choices=DISCOUNT_CHOICES)
discount = models.DecimalField(max_digits=7, decimal_places=2, verbose_name=_('Discount'))
forms.py
class AdminDiscountForm(ModelForm):
class Meta:
model = Discount
exclude = ('company',)
views.py
def add_discount(request, company_id=None):
company = get_object_or_404(Company, company_id)
discount=Discount(company=company)
if request.method == 'post':
form = AdminDiscountForm(request.POST, instance=discount)
if form.is_valid():
form.save()
return HttpResponse('Success')
else:
form = AdminDiscountForm(instance=company)
context = { 'company':company,
'form':form,}
return render_to_response('add-discount.html', context,
context_instance=RequestContext(request))
This works by creating an instance of your discount model, then binding your form to this instance. This instance is not saved to your db but used to bind the form. This bound form has a value for company of the bound instance. It is then sent to your template for the user to fill out. When the user submits this form, and the form is validated, the model validation check will check for uniqueness of the unique together defined in Meta.
See Model Validation Docs and overriding clean for ModelForms
edit:
You can do a couple of things to catch non unique together entry attempts.
Inside your form.is_valid() you can except an Integrity Error like this:
if request.method == 'post':
form = AdminDiscountForm(request.POST, instance=discount)
if form.is_valid():
try:
form.save()
return HttpResponse('Success')
except IntegrityError:
form._errors["company"] = "some message"
form._errors["type"] = "some message"
else:
...
Use self.instance within the model form's clean method to check for uniqueness.
You could try this:
discount = Discount(company = blah)
form = AdminDiscountForm(request.POST, instance=discount)
if form.is_valid():
discount = form.save()
And the docs say: By default the clean() method validates the uniqueness of fields that are marked as ... unique_together
I have a this model:
class Fleet(models.Model):
company = models.ForeignKey("Company", editable=False)
aircraft = models.ForeignKey("Aircraft")
size = models.IntegerField(default=1)
description = models.TextField(blank=True)
def __unicode__(self):
return u"%s" % (self.aircraft, )
And then a form based on this model:
class FleetForm(ModelForm):
class Meta:
model = Fleet
exclude = ('company', )
When I use this form in a template, the "company" field is not added, which is expected. But that field is required as blank != True.
The way I use this form, the company attribute will always be known in the view function, as it's passed from the URL. How can I add the company to the form in the view function before I save it?
Here is my view:
def new_fleet(request, company_id):
from forms import FleetForm
company = Company.objects.get(pk=company_id)
if request.method == "POST":
form = FleetForm(request.POST,)
form.company = company #doesn't work
form = form.save(commit=False) #can't do this because the form
form.company = company #doesn't validate (company is not set)
if not form.errors:
form.save()
else:
fleet = Fleet(company=company) #pointless because the company widget
form = FleetForm(instance=fleet) #isn't created but eh whatever
There are two ways to solve this issue:
Instantiate your model with initial values for the missing, but required fields:
company = Company.objects.get(pk=company_id)
fleet = Fleet(company=company)
form = FleetForm(request.POST, instance=fleet)
new_fleet = form.save()
Use save(commit=False) and manually set any extra required fields:
company = Company.objects.get(pk=company_id)
form = FleetForm(request.POST)
fleet = form.save(commit=False)
fleet.company = company
new_fleet = fleet.save()
See the note in this section of the ModelForm API documentation for more details.
By the way, either editable=False or exclude is enough to remove a field from a ModelForm; you don't need both.
in #Ayman Hourieh 's answer . Just to address a pitfall in Django. If you have many-to-many field in the form. it would not get saved here. You should explicitly call save_m2m() . add one more line as below.
form.save_m2m()