modelform id field being passed as REQUIRED field - django

The site I am building uses modelforms several times. Until now validation of the forms using is_valid has gone smoothly. Now, a new model I've created has decided that the id field (which is generated automatically for model forms) is a required field. I am rendering the fields manually in the template and am not rendering the id field. This has not been a problem with any of my modelforms until now, since it has never been registered as a required field. With this problematic model however, since I am not rendering the field, it gets returned empty and so doesn't pass validation.
I realize that I could solve the problem by rendering the field, but I'd like to understand whats going on here. Why has it decided, seemingly randomly, that the id is required?
Here is the problematic model:
class Item(models.Model):
budgetcatagory=models.ForeignKey(BudgetCatagory, on_delete=models.CASCADE)
name=models.CharField(max_length=30)
enName=models.CharField(max_length=30, default ="")
detail=models.CharField(max_length=30)
layout=models.CharField(max_length=30, default="normal")
unit=models.CharField(max_length=30)
unit_description=models.CharField(max_length=30, default="")
unit_price=models.IntegerField(default=0)
QTY=models.IntegerField(default=0)
param1=models.CharField(max_length=30, blank=True)
param2=models.CharField(max_length=30, blank=True)
param3=models.CharField(max_length=30, blank=True)
param4=models.IntegerField(default=0)
parent=models.CharField(max_length=30, default = "0")
cost_ave=models.IntegerField(default=0, blank=True)
cost_min=models.IntegerField(default=0, blank=True)
cost_max=models.IntegerField(default=0, blank=True)
total_cost=models.IntegerField(default=0)
choiceList=(
('choice1',param1),
('choice2',param2),
)
ItemChoice=models.CharField(
max_length=10,
choices=choiceList,
default='',
)
objects=ItemManager()
def __str__(self):
return self.name
Here is how I am populating the form before sending to template:
else:
#populate
I=Item.objects.filter(budgetcatagory__user_id=U.id)
C=BudgetCatagory.objects.filter(user_id=U.id)
#initiate initial catagories and items for new user
if (not I.exists()) or (not C.exists()):
Item.objects.filter(budgetcatagory__user_id=U.id).delete()
BudgetCatagory.objects.filter(user_id=U.id).delete()
InitiateNewUser(U)
I=Item.objects.filter(budgetcatagory__user_id=U.id)
C=BudgetCatagory.objects.filter(user_id=U.id)
FormsetItem=ItemFormSet(queryset=I)
FormsetCat=CatFormset(queryset=C)
return render(request,'getdata/budgetmachine.html', {'FormsetItem':FormsetItem, 'FormsetCat':FormsetCat })
And here is how I am populating the form from the POST:
if request.method=='POST':
#Save
FormsetItem=ItemFormSet(request.POST,queryset=Item.objects.filter(budgetcatagory__user_id=U.id))
FormsetCat=CatFormset(request.POST)
if FormsetItem.is_valid():
I've been breaking my head against this for days. Any help would be appreciated.
Thanks
EDIT:
Alasdair, following your answer I renderered the entire formset automatically {{ formset }} to ensure that all necessary fields would be rendered. It is now failing validation for an even weirder reason:
{'id': ['Select a valid choice. That choice is not one of the available choices.']}
Obviously I haven't set up my id as a choice field as I haven't set it up at all. it gets generated automatically. I am slowly going insane! Any help would be more than welcome.

The id field is required for model formsets, you have to include it in the template. Perhaps you were using individual model forms before, where it isn't required.

Related

ReverseManyToOne create object from children through django form

Using:
Python 3.7.3
django 2.2.5
mysql 5.7.27
I have the following models:
class Item(models.Model):
...
class Comments(models.Model):
Item = models.ForeignKey('Item', default=None, null=True, on_delete=models.CASCADE)
Comment = models.TextField(max_length=512, default="", blank=True)
I would like to create a Comments object, when creating the Item through a django form. I tried to do:
class ItemInsertForm(forms.ModelForm):
...
Comments = forms.CharField(required=False,
widget=forms.Textarea(attrs={'placeholder':"Use comments to describe item details \
or issues that other users should know",
'rows':5,
'cols':50,
}
)
)
def clean_Comments(self, *args, **kwargs):
_comment = self.cleaned_data.get('Comments')
_comments = Item.comments_set.create(Comment=_comment)
return _comments
but I get the following error:
'ReverseManyToOneDescriptor' object has no attribute 'create'
Both tables are empty, so no Item and no Comments currently exist. I guess that's why there is no 'create' method available. Is there a way I could achieve what I want?
Or is there another way to manage the comments for the Item object? I created another table to be able to associate multiple comments to the same item and differentiate between them. A character field in the Item class would concatenate all comments in a single string.
I see quite some issues with your code, but since I feel like you gave it an honest shot, I'll try to help you out as best as I can.
First of all your models.py file:
Model names should be singular, so instead of Comments, use Comment.
Class members should be lowercase, so Item and Comment should be changed to item and comment.
Comment.comment is still not very descriptive. The comment is the actual object, it's content is the text within the comment, so text would be more appropriate here.
A ForeignKey with null=True already sets default to None.
Taking this into account and cleaning up your models.py:
class Item(models.Model):
...
class Comment(models.Model):
item = models.ForeignKey(Item, null=True, on_delete=models.CASCADE)
text = models.TextField(max_length=512, default="", blank=True)
Then, moving on to your form:
Since it's a form for creation Comments, a more appropriate name would be CommentForm.
def clean_Comments(self, *args, **kwargs): is a function reserved for doing validation on the Comments field, not for creating an object from the form input. For that you can use the ModelForm's save() method. You only need to define a save method if you're going to perform some custom logic though.
Let's fix those issues first, before I move onto the error message your getting:
class ItemInsertForm(forms.ModelForm):
class Meta:
model = Comment
fields = ['text']
text = forms.CharField(required=False,
widget=forms.Textarea(attrs={'placeholder':"Write your comment to describe item details \
or issues that other users should know",
'rows':5,
'cols':50,
}
)
)
This form, when submitted, will create a Comment object. However, there is still no ability to add the comment to an Item.
To do this, you need to make sure there are Item instances in the database, or allow the user to create one through an ItemForm
There are multiple ways to do this:
Add a ModelChoiceField to the CommentForm, which will allow the user to select an item from a select.
item = forms.ModelChoiceField(queryset=Item.object.all(),
to_field_name = '<item_name>',
empty_label="Select an Item")
When you want to add this form to an something like an ItemDetailPage, you can use the currently viewed Item using something like
item = Item.objects.get(pk=<item_id>)
or
item = Item.objects.create(<item_properties_here>)
then, when saving your form:
comment = form.save()
comment.item = item.
comment.save()
The third way is what you were trying, and why you were getting an error. Retrieve an item, then add the comment saved from the form to the item.comment_set.
Something like this:
item = Item.objects.get(pk=<item_id>)
comment = form.save()
item.comments_set.add(comment)
item.save()

Adding/deleting fields in the same ModelForm django

I have little experience in django so I would really appreciate your help!
In general, I have created a ModelForm which depending on the users who is logged in, he changes some values in the field. So consider that this form is being edited about 5 times by 5 different users.
I would like to show a specific field in the template (and view) only when the third user is logged in.
My model is :
class Task(models.Model):
Taskdetails = models.CharField(max_length=500, null=True)
asset = models.ForeignKey('Asset', null=True)
failure = models.ForeignKey('Failure', null=True)
cause = models.ForeignKey('Cause', null=True)
Created_task_date = models.DateTimeField(default=timezone.now, null=True)
employee = models.ForeignKey("auth.User", null = True)
and also i have created this ModelForm
class Meta:
model = Task
fields = ('Taskdetails', 'asset', 'failure', ,'employee','cause',)
Also I have 5 edititions of the TaskForm in which is user edits something.
The thing I am trying to do is to show the cause field only in the third form.
I tried to exclude the value but nothing apperas.
If i include the field cause (just like above), I must "pass" it in the template in order to be edited from the first user (otherwise the task_form is not saved)
I hope I became clear.
I would really appreciate your help.

Filter M2M in template?

In my model, I have the following M2M field
class FamilyMember(AbstractUser):
...
email_list = models.ManyToManyField('EmailList', verbose_name="Email Lists", blank=True, null=True)
...
The EmailList table looks like this:
class EmailList(models.Model):
name = models.CharField(max_length=50, default='My List')
description = models.TextField(blank=True)
is_active = models.BooleanField(verbose_name="Active")
is_managed_by_user = models.BooleanField(verbose_name="User Managed")
In the app, the user should only see records that is_active=True and is_managed_by_user=True.
In the Admin side, the admin should be able to add a user to any/all of these groups, regardless of the is_active and is_managed_by_user flag.
What happens is that the Admin assigns a user to all of the email list records. Then, the user logs in and can only see a subset of the list (is_active=True and is_managed_by_user=True). This is expected behavior. However, what comes next is not.
The user deselects an email list item and then saves the record. Since M2M_Save first clears all of the m2m records before it calls save() I lose all of the records that the Admin assigned to this user.
How can I keep those? I've tried creating multiple lists and then merging them before the save, I've tried passing the entire list to the template and then hiding the ones where is_managed_by_user=False, and I just can't get anything to work.
What makes this even more tricky for me is that this is all wrapped up in a formset.
How would you go about coding this? What is the right way to do it? Do I filter out the records that the user shouldn't see in my view? If so, how do I merge those missing records before I save any changes that the user makes?
You might want to try setting up a model manager in your models.py to take care of the filtering. You can then call the filter in your views.py like so:
models.py:
class EmailListQuerySet(models.query.QuerySet):
def active(self):
return self.filter(is_active=True)
def managed_by_user(self):
return self.filter(is_managed_by_user=True)
class EmailListManager(models.Manager):
def get_queryset(self):
return EmailListQuerySet(self.model, using=self._db)
def get_active(self):
return self.get_queryset().active()
def get_all(self):
return self.get_queryset().active().managed_by_user()
class EmailList(models.Model):
name = models.CharField(max_length=50, default='My List')
description = models.TextField(blank=True)
is_active = models.BooleanField(verbose_name="Active")
is_managed_by_user = models.BooleanField(verbose_name="User Managed")
objects = EmailListManager()
views.py:
def view(request):
email = EmailList.objects.get_all()
return render(request, 'template.html', {'email': email})
Obviously there is outstanding data incorporated in my example, and you are more than welcome to change the variables/filters according to your needs. However, I hope the above can give you an idea of the possibilities you can try.
In your views you could do email = EmailList.objects.all().is_active().is_managed_by_user(), but the loading time will be longer if you have a lot of objects in your database. The model manager is preferred to save memory. Additionally, it is not reliant on what the user does, so both the admin and user interface have to talk to the model directly (keeping them in sync).
Note: The example above is typed directly into this answer and has not been validated in a text editor. I apologize if there are some syntax or typo errors.

Django - ChoiceField Spanning Multiple Forms

Is it possible in django to have a ChoiceField on a formset level rather than an inline form? So for example if I have a formset for phones and each inline form represents a single Phone model, how can I have a ChoiceField that spans all the inline forms? Something like this where I'm choosing a primary phone:
My models:
class Profile(models.Model):
verified = models.BooleanField(default=False)
primary_phone = models.OneToOneField('Phone', related_name='is_primary', null=True, blank=True)
class Phone(models.Model):
profile = models.ForeignKey(Profile, editable=False)
type = models.CharField(choices=PHONE_TYPES, max_length=16)
number = models.CharField(max_length=32)
#property
def is_primary(self):
return profile.primary_phone == self
I can always remove primary_phone and use a BooleanField in Phone to indicate if it's primary or not, but I'm not sure if this going to help my problem.
I'm also looking for a less-hacky more-django-like approach if possible.
There is no way to have django create this for you automatically. In your ModelForm (that's used in the inline) I'd add a boolean field called is_primary. This field will then show up on each inlined Phone instance (as a checkbox).
On the front end sort it out with javascript so that a user can only select one default at a time. On the back end use some custom validation to double check that only one is_default was submitted, and then update the primary_phone as necessary with form logic.

Django Models Manager for saving custom processed data in a field

A noob here. I have a model class where I want to save something processed in one of the fields of that table. am trying to use a ModelManager for that but do not know if it is possible or how to.
I want to save a custom url for each post here. So I want to have a method in PostManager class which will calculate hash of something (say current time) and save it as a url. I could not find any syntax help so asking it here.
class Post (models.Model):
name = models.CharField(max_length=1000, help_text="required, name of the post")
description = models.TextField(blank=True)
created_datetime = models.DateTimeField(auto_now_add=True, editable=False)
modified_datetime = models.DateTimeField(auto_now=True, editable=False)
custom_hashed_url = models.CharField(unique=True, max_length=1000, editable=False)
def save(self, *args, **kwargs):
#How to refer to the custom_hashed_url in the Post class?
super(Model, self).save()
If you want the url to be saved in the database with the rest of the information, it will need to appear in the model as a field.
Change the url to an appropriate field type and set its 'editable' attribute to False, as you've done with the datetime fields. This will stop it appearing in forms.
You could then override the model's save method (see Django docs) so that it calculates the post's url and adds it automatically as the instance is saved!
Model managers are used for 'model level' interactions that work with many instances, or sets of instances. In this case you are trying to manipulate a single instance. We use a field to store the information in the database for the record and a method (in this case overriding a built-in method to hook into the default behaviours) to calculate the field's value.
Good luck!