When I load the page, the value of the input is automaticly this:
How is that possible?
views file
if request.method == 'POST':
form = FieldForm(request.POST, instance=Field(user=request.user))
if form.is_valid():
obj = form.save(commit=False)
obj.creator_adress = get_client_ip(request)
obj.save()
return redirect('/dashboard')
else:
form = FieldForm(instance=Field)
......
forms file
class FieldForm(forms.ModelForm):
class Meta:
model = Field
fields = (
'title',
'url'
)
models file
class Field(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
default=None,
null=True,
on_delete=models.CASCADE,
)
title = models.CharField(max_length=255)
url = models.CharField(max_length=255)
creator_adress = models.GenericIPAddressField(null=True)
def __str__(self):
return str(self.user)
Here one input 😂
value= <django.db.models.query_utils.DeferredAttribute object at 0x000001E180304250>
In your view you are passing a reference to your Field model as the argument instance. Doing this places the representation of the model fields into your form fields when it is rendered. If what you are wanting is just a blank form when you load the page then just remove the instance argument and create your form in your else statment, like form = FieldForm(). The instance argument is only needed for a form if you are trying to pre-populate data into the form, for example if you made a view where you wanted to update information on an already created object you would pass an instance of your model to the instance argument.
Related
I creating an app to keep pets info.
I'm displaying a form to insert data with a user profile. I don't want to display the parent option because that should be saved automatically but have no idea how to do that. If I exclude the parent, in the form doesn't appear but the pet's info is not linked to the user.
forms.py:
class PetForm(ModelForm):
class Meta:
model = Pet
exclude = ('parent',)
models.py:
class Pet(models.Model):
pet_name = models.CharField(max_length=200)
parent = models.ForeignKey(Profile, on_delete=models.CASCADE, null=True, blank=True)
def __str__(self):
return self.pet_name
Yes, you can work with commit=False while saving in the form in POST method, see an example below.
Try this view:
views.py
def some_view_name(request):
if request.method=="POST":
form = PetForm(request.POST)
if form.is_valid():
pet = form.save(commit=False)
pet.parent = request.user.profile.id
pet.save()
else:
form = PetForm()
return render(request,'any_folder_name/any_file.html',{"form":form})
you need to add this in your view.
form = PetForm(request.POST)
if form.is_valid():
instance = form.save(commit=False)
instance.parent = parent
instance.save()
of course you should specify parent object too.
I have a Campaign Model, and a CampaignCreateForm which is a ModelForm. The Campaign model has a contact_list field of type JSONField. When a user is creating a campaign using the CampaignCreateForm they upload a CSV file which is processed to create the JSON data for the contact_list field.
What is the best way to approach this so that I can test the form separately from the view?
I've built this using a CampaignCreateView which inherits from CreateView, and included the logic to parse the CSV file and create the JSON data in the views form_valid method, but this makes unit-testing the form (and any form field validation) impossible. I want to test the functions included in the forms clean method. With my current approach the view and form must be tested together, and that feels wrong.
How can I create the form such that all the logic for the form (processing the CSV file to create the JSON data and discarding the uploaded file) is handled in the form only?
My current CreateView and ModelForm are below:
View:
class CampaignCreateView(LoginRequiredMixin, CreateView):
model = Campaign
form_class = CampaignCreateForm # required if you want to use a custom model form, requires `model` also
template_name = "writing/campaign_create.html"
def get_success_url(self):
""" If model has get_absolute_url() defined, then success_url or get_success_url isnt neccessary
"""
user = User.objects.get(username=self.kwargs.get("username"))
return reverse("writing:campaigns", kwargs={"username": user.username})
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs.update({"user": self.request.user})
return kwargs
def form_valid(self, form):
""" by default, form_valid redirects to success_url
"""
form.instance.user = self.request.user
form.instance.image_url = f"https://picsum.photos/seed/{randrange(10000)}/500/300"
file = form.cleaned_data["contact_list_file"]
file_content = file.open("r")
json_contact_list = csv_to_json(file_content)
form.instance.contact_list = json_contact_list
contact_list = json.loads(json_contact_list) # as python dict
form.instance.items = len(contact_list)
response = super().form_valid(form)
log_campaign_progress(pk=form.instance.pk, status="t2h-created", stage="campaign")
enqueue_handwriting_generation(campaign_pk=form.instance.pk)
return response
Form:
class CampaignCreateForm(forms.ModelForm):
contact_list_file = forms.FileField(required=True)
def __init__(self, *args, **kwargs):
self.user = kwargs.pop(
"user"
) # To get request.user. Do not use kwargs.pop('user', None) due to potential security hole
super().__init__(*args, **kwargs)
class Meta:
model = Campaign
fields = ("name", "message", "contact_list_file")
def clean(self):
# Cammpaign name is unique for the user
try:
Campaign.objects.get(name=self.cleaned_data["name"], user=self.user)
except Campaign.DoesNotExist:
pass
else:
self.add_error(
"name",
ValidationError(
_("You've already created a campaign with this name"), code="BadCampaignName"
),
)
# if an error is attached to a field then the field is removed from cleaned_data
self = check_message_length(self)
self = check_message_text_is_valid(self)
self = check_file_is_valid(self)
self = check_message_tags_exist_in_contact_list(self)
return self.cleaned_data
Model:
# TimeStampedModel inherits form models.Model
class Campaign(TimeStampedModel):
user = models.ForeignKey(
User,
on_delete=models.CASCADE,
)
order = models.ForeignKey(
Order,
on_delete=models.SET_NULL,
null=True,
)
name = models.CharField(max_length=80)
notes = models.CharField(max_length=2000, null=False, blank=True)
message = models.TextField(
max_length=1800,
null=False,
blank=False,
)
contact_list = models.JSONField(null=True)
items = models.PositiveIntegerField(null=False, blank=False)
purchased = models.BooleanField(default=False, null=False)
def __str__(self):
return self.name
class Meta:
ordering = ["-modified"]
unique_together = ("user", "name")
... more fields
I created a model for registered users and added a field feedback in it. I need to allow logged in users to post feedback and to get it updated in the model, but it is not. Instead it is getting created as a new entry with a primary key.
model for register
class UserProfile(models.Model):
user = models.OneToOneField(User,on_delete=models.CASCADE, default=None, null=True)
role = models.CharField(max_length=50, choices=Roles, default='client')
feedback = models.TextField(max_length=500,blank=True)
verified =models.BooleanField(default = False,blank=True)
def __str__(self):
return self.user.username
form for feedback:
class FeedbackForm(forms.ModelForm):
class Meta():
model = UserProfile
fields = ('feedback',)
view for feedback:
#login_required
def feedback(request):
if request.method == "POST":
form = FeedbackForm(request.POST)
else:
form = FeedbackForm()
if form.is_valid():
userprofile=form.save(request)
userprofile.save()
else:
form = FeedbackForm()
return render(request, 'NewApp/feedback.html',{'form':form})
You need to pass in the current profile as the instance parameter.
form = FeedbackForm(request.POST, instance=request.user.userprofile)
I have a simple Group model that users can be added.
class Group(models.Model):
name = models.CharField(max_length=50)
users = models.ManyToManyField(settings.AUTH_USER_MODEL)
created_by = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE,
related_name='admin_on_group')
date_created = models.DateTimeField(auto_now_add=True)
date_modifies = models.DateTimeField(auto_now=True)
def __str__(self):
return self.name
I have a basic CreateView for the group. The logged in user who creates the group get saved in the field created_by. I however ALSO want to save the same logged in user in the field users so that he can participate as a normal member of the group. The problem is that the view ends up ONLY saving the logged in user and the other users passed in from the form field users are not saved.
For example, If a user called 'george' creates a group, he should be added in the created_by and users as well. As of now, when I select other users in the form, only george gets saved in both fields.
class GroupCreateView(CreateView):
form_class = GroupForm
template_name = "groups/group_create.html"
def form_valid(self, form):
form = form.save(commit=False)
form.created_by = self.request.user
form.save()
# Apparently you can only add M2M relationships saves after first
# saving
form.users.add(User.objects.get(pk = self.request.user.pk))
return HttpResponseRedirect(reverse('group_list'))
def get_form_kwargs(self):
kwargs = super(GroupCreateView, self).get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
I have a modelForm that has the following outline.
Note: The initial data passed in the self.fields['users'] below also doesn't show. I have also used a custom model that has phone_number as the USERNAME_FIELD. The querysets passed in the self.fields['users'] works.
class UserModelChoiceField(forms.ModelMultipleChoiceField):
def label_from_instance(self, obj):
return obj.get_full_name()
class GroupForm(forms.ModelForm):
class Meta:
model = Group
fields = ('name', 'users', )
def __init__(self, *args, **kwargs):
# popping the user from kwargs dictionary that has been
# passed in CreateView
user = kwargs.pop('user', None)
self.user = user # setting self.user to be equal to user above
super(GroupForm, self).__init__(*args, **kwargs)
self.fields['users'] = UserModelChoiceField(
queryset=User.objects.exclude(phone_number=str(user)),
initial=User.objects.get(phone_number=str(user))
)
Since you've saved the form with commit=False, you need to call the form's save_m2m() method to save the many-to-many data after you have saved the instance.
def form_valid(self, form):
instance = form.save(commit=False)
instance.created_by = self.request.user
instance.save()
form.save_m2m()
# Apparently you can only add M2M relationships saves after first
# saving
instance.users.add(self.request.user)
return HttpResponseRedirect(reverse('group_list'))
Note that I've changed the line to instance = form.save(commit=False) to make it clearer that save() returns an instance, and so that you still have access to the form.
I have a model named Project containing three ManyToManyFields (member_student, supervisor and tag). One (tag) of which is excluded in the form and it has to be saved manually. So, in the view, I use save(commit=False) because I have to change some fields of the form. After changing the fields, the form is saved and I add the tags one by one. Then, when I call save_m2m to save ManyToManyField, I get the error given by the save_m2m line in views:
invalid literal for int() with base 10: 'a'
Here is my model.
class Tag(models.Model):
name = models.CharField(unique=True, max_length=60)
slug = models.SlugField(max_length=60, unique=True)
def save(self, *args, **kwargs):
if not self.id:
self.slug = slugify(self.name)
super(Tag, self).save(*args, **kwargs)
class Project(models.Model):
'''Main Project uploading'''
title = models.CharField(max_length=300)
description = models.TextField(null=True)
#year = models.ForeignKey(Year)
tag = models.ManyToManyField(Tag)
owner_student = models.ForeignKey(Student, related_name='member_student')
member_student = models.ManyToManyField(Student, blank=True, null=True)
supervisor = models.ManyToManyField(Supervisor, blank=True, null=True)
subject = models.ForeignKey(Subject)
main_document = models.FileField(upload_to='main_documents/')
supporting_document = models.FileField(upload_to='supp_documents/', blank=True, null=True)
source_code = models.FileField(upload_to='source_code/', blank=True, null=True)
screenshot = models.ImageField(upload_to='screenshots/', blank=True, null=True)
This is the forms.py:
class ProjectForm(forms.ModelForm):
class Meta:
model = Project
exclude = ['owner_student', 'slug', 'tag']
tag = forms.CharField(max_length=100)
Here is the View.
def add_project(request):
parameters = {}
if request.method =="POST":
upload_form = ProjectForm(request.POST, request.FILES)
if upload_form.is_valid():
new_form = upload_form.save(commit=False)
mystud = Student.objects.get(user=request.user)
new_form.owner_student = mystud
new_form.save()
tags = upload_form.cleaned_data['tag']
tags = tags.split(',')
for eachtag in tags:
tag, created = Tag.objects.get_or_create(name=eachtag.strip())
tag.save()
new_form.tag.add(tag)
upload_form.save_m2m()
return HttpResponseRedirect(reverse(project_page, args=(new_form.slug,)))
else:
parameters["upload_form"] = upload_form
return render_to_response('upload.html', parameters)
else:
upload_form = ProjectForm()
parameters["upload_form"] = upload_form
parameters["page_title"] = "Upload your Project"
return render_to_response('upload.html', parameters)
So, my question is how can I save the tags as well as the two other ManyToManyField without getting error ? I guess the save_m2m function is giving error because of the tuple returned by get_or_create.
Don't use 'tag' as the name of the charfield on your form. That'll cause save_m2m to think it needs to use the values in the charfield to set the related 'tag' field on the object.
Internally, save_m2m goes through each many-to-many field in the model. It checks for the presence of data under that name in the form's cleaned_data dictionary, and if present has the model field object update record the contents using the field's save_form_data method. It trusts the form field to have returned the right type of Python object. In this case, your charfield is returning a string (as expected), but it's incorrect to assign a string to a many-to-many field.