Django creation ModelForm with OneToOneField hidden but required - django

So I have a model:
class MyThing(models.Model):
my_field = OneToOneField(SomeOtherModel)
... other fields
A form:
class MyThingForm(forms.ModelForm):
class Meta:
model = MyThing
A view:
class MyThingView(views.TemplateView):
template_name = 'thing.html'
def get(self, request, *args, **kwargs):
form = MyThingForm()
return render(self.template_name, {'form': form})
def post(self, request, *args, **kwargs):
... retrieve some_instance
request.POST['my_field'] = some_instance
form = MyThingForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect(...somewhere else)
return render(self.template_name, {'form': form})
my thing.html template:
{% for field in form %}
{{ field }}
{% endfor %}
What my problem is:
I need to hide the my_field field when rendering the template (but from backend), that implying that when I do the for on form in the template, it shouldn't have the my_field field in the fields set already
This is a creation form, that means that I don't have an existing instance
In the backend the my_field is required, so when doing POST I retrieve the instance for my_field from somewhere, doesn't matter where, and add it to the data for the form in sight. After this the form should be valid and can be saved to database
So the basic question is : How do I make a required field, hidden but saveable?

It very usual use case look at this doc.
In summary you can exclude the field from form and save it after retrieving it from somewhere.
Update code as
class MyThingForm(forms.ModelForm):
class Meta:
model = MyThing
exclude = ['my_field', ]
class MyThingView(views.TemplateView):
...
def post(self, request, *args, **kwargs):
form = MyThingForm(request.POST)
#retrieved_my_field = retrieve the field
if form.is_valid():
inst = form.save(commit=False)
inst.my_field = retrieved_my_field
inst.save()
return HttpResponseRedirect(...somewhere else)
return render(self.template_name, {'form': form})

Related

How to access form object before and after saving in django-bootstrap-modal-forms

I have following code in my view of adding a new Item. Some fields are filled via user some fields are filled in the background. If form is valid then user is redirected to a url with a parameter (slug) from added object. How can I convert this code to django-bootstrap-modal-forms way?
def category_view(request, slug, *args, **kwargs):
...
if request.POST:
form = CreateItemForm(request.POST)
if form.is_valid():
if not request.user.is_authenticated:
raise PermissionDenied()
obj = form.save(commit=False)
obj.created_country = Constants.country_code
obj.created_by = request.user
obj.save()
return redirect('category:item_detail', slug=obj.slug)
I used django-bootstrap-modal-forms in the below way. but country and user fields are not null and must be filled. These fields are not part of the form.
class add_person(BSModalCreateView):
template_name = 'add_item.html'
form_class = CreateItemForm
success_message = 'Success: Item was created.'
success_url = reverse_lazy('category:item_detail') # slug needed
You are asking, how to modify the form and the only code you do not provide is the form. But try something like this:
forms.py
class BaseForm(forms.BaseForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field in self.fields.values():
if isinstance(field.widget, widgets.RadioSelect):
continue
elif isinstance(field.widget, widgets.Select):
field.widget.attrs.update({'class': 'form-select'})
continue
field.widget.attrs.update({'class': 'form-control'})
class CreateItemForm(BaseForm):
# your code
This itereates over your FormFields and adds the bootstrap class form-select, or much more important form-control to the widget of your field.

Model Formset and regular ModelForm in same template?

I got two models:
Project:
class Project(Model):
name = CharField(max_length=50)
members = ManyToManyField("accounts.User", through='ProjectUser')
organization = ForeignKey(Organization, related_name="projects", on_delete=CASCADE)
def __str__(self):
return self.name
and Task:
class Task(Model):
task = CharField(max_length=100)
project = ForeignKey(Project, on_delete=CASCADE)
class Meta:
db_table = 'task'
I got a UpdateView class:
class ProjectUpdateView(UpdateView):
form_class = ProjectUpdateForm
template_name = 'projects/project_edit.html'
success_url = reverse_lazy('projects:list')
How can I allow a user to add tasks (through an inline formset) on the same page as where they'd edit a Project instance?
E.g one consolidated form where the user can edit the Project name, and add / remove Task instances, all in one place
Form/Formset:
First, create a form and a formset for your Task model
class TaskForm(ModelForm):
class Meta:
model = Task
fields = ['task']
def __init__(self, *args, **kwargs):
super(TaskForm, self).__init__(*args, **kwargs)
class TaskBaseFormSet(BaseInlineFormSet):
def __init__(self, *args, **kwargs):
super(TaskBaseFormSet, self).__init__(*args, **kwargs)
TaskFormset = inlineformset_factory(
Project, # parent_model
Task, # model
form=TaskForm,
formset=TaskBaseFormSet
)
Or maybe all that you need to do to create a TaskFormset if you dont need a TaskForm class is this
TaskFormset = inlineformset_factory(Project, Task, fields=('task',))
View:
I see you're using a UpdateView class for your view, so you can do this to get a TaskFormset in your context_data, so now you can use the TaskFormset in the template that you declared in the 'template_name' property of your UpdateView class
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
if self.request.POST:
context['task_formset'] = forms.TaskFormset(self.request.POST)
else:
context['task_formset'] = forms.TaskFormset()
return context
# In the form_valid method of your UpdateView class you can validate the data
# and assign the Project instance to all the tasks that were create by the formsets
def form_valid(self, form):
task_formset = context['task_formset']
# you can validate formset data like this
if not task_formset.is_valid():
return self.form_invalid(form)
project = form.save()
# Here you assign the Project instance to the Tasks
task_formset.instance = project
task_formset.save()
return super().form_valid(form)
Template:
Now all that you need to do is to print the management_form and each form from the formset using a loop as you can see in the code below
<form method="post">
<!-- Your ProjectUpdateForm form here... -->
{{ task_formset.management_form }}
<table>
{% for form in task_formset %}
{{ form }}
{% endfor %}
</table>
</form>
Hope this can help! There are some links to the official Django documentation that you may find useful:
https://docs.djangoproject.com/en/3.1/topics/forms/formsets/#using-a-formset-in-views-and-templates
https://docs.djangoproject.com/en/3.1/topics/forms/modelforms/#inline-formsets
https://docs.djangoproject.com/en/3.1/ref/forms/models/#inlineformset-factory

<News:somethinng>" needs to have a value for field "id" before this many-to-many relationship can be used

Here from my template I am getting the list of strings with getlist in a view. I want to assign these list in my ManyToMany field.I am getting the selected multiple list in my view and it is also creating the reporter object from these list which is fine.
Now I want to assign these only selected multiple options in my many to many field.
How can I do it here? I get this error while saving the form
"<News:somethinng>" needs to have a value for field "id" before this many-to-many relationship can be used.
views
class NewsCreateVView(View):
template_name = 'add_news.html'
def get(self, request):
form = CreateNewsForm()
return render(request, self.template_name, {'form': form})
def post(self, request, **kwargs):
form = CreateNewsForm(request.POST)
reporters = request.POST.getlist('reporter')
if form.is_valid():
news = form.save(commit=False)
for reporter in reporters:
obj = Reporter.objects.create(name=reporter)
news.reporter.add(obj.pk)
news.save()
return redirect('list_news')
models
class Reporter(models.Model):
name = models.CharField(max_length=255)
created = models.DateTimeField(auto_now_add=True)
class News(models.Model):
title = models.CharField(max_length=255)
reporter = models.ManyToManyField(Reporter, related_name='reporters')
template
<select class="form-control" name="reporter" multiple="multiple">
As the error says, news first needs to be saved in order to have a primary key, so:
class NewsCreateVView(View):
template_name = 'add_news.html'
def get(self, request):
form = CreateNewsForm()
return render(request, self.template_name, {'form': form})
def post(self, request, **kwargs):
form = CreateNewsForm(request.POST)
reporters = request.POST.getlist('reporter')
if form.is_valid():
news = form.save()
pks = [Reporter.objects.create(name=reporter).pk
for reporter in reporters]
news.reporters.add(*pks)
return redirect('list_news')

Django ModelForm not saving data even though form.save is executed

I have a website where user have 2 model for their profile, user_detail and user_location. I tried to serve 2 model form on one page with one submit. The problem is when the data from those model form does not save in to the database.
I confirmed that self.request.POST in the post method returns the correct data.
I tried :
Django ModelForm not saving data to database - Does not work
Django ModelForm not saving data - Does not work
The following code if for admins.
Here is my view :
class UpdateProfile(LoginRequiredMixin, UpdateView):
template_name = 'account/user_profile.html'
fields = '__all__'
model = models.UserProfile
user_detail_form_class = forms.UserDetailForm
user_location_form_class = forms.UserLocationForm
def get_context_data(self, **kwargs):
user_profile = get_object_or_404(models.UserProfile, pk=self.kwargs.get(self.pk_url_kwarg))
context = super(UpdateProfile, self).get_context_data(**kwargs)
if 'user_detail_form' not in context:
context['user_detail_form'] = self.user_detail_form_class(instance=user_profile.user_detail)
if 'user_location_form' not in context:
context['user_location_form'] = self.user_location_form_class(instance=user_profile.user_location)
return context
def get(self, request, *args, **kwargs):
super(UpdateProfile, self).get(request, *args, **kwargs)
return self.render_to_response(self.get_context_data())
def post(self, request, *args, **kwargs):
user_detail_form = self.user_detail_form_class(request.POST)
user_location_form = self.user_location_form_class(request.POST)
if user_detail_form.is_valid() and user_location_form.is_valid():
user_detail_form.save()
user_location_form.save()
return redirect(self.get_success_url())
else:
return self.render_to_response(self.get_context_data())
def get_success_url(self):
return reverse('account:admin_client_list')
def dispatch(self, request, *args, **kwargs):
if not request.user.groups.filter(name__in=['Admin']).exists():
return errors.render_403(request)
return super(UpdateProfile, self).dispatch(request, *args, **kwargs)
Here is my template :
{% extends 'base.html' %}
{% block content %}
<form method='POST' action="">{% csrf_token %}
{{ user_detail_form }}
{{ user_location_form }}
<input type="submit" value="Submit">
</form>
{% endblock %}
Here is the form :
class UserDetailForm(forms.ModelForm):
class Meta:
model = models.UserDetail
fields = '__all__'
class UserLocationForm(forms.ModelForm):
class Meta:
model = models.UserLocation
fields = '__all__'
You need to pass the instance parameter when you are creating the ModelForm in the post method. Sample code:
user_profile = get_object_or_404(models.UserProfile, pk=self.kwargs.get(self.pk_url_kwarg))
user_detail_form = self.user_detail_form_class(request.POST, instance=user_profile.user_detail)
user_location_form = self.user_location_form_class(request.POST, instance=user_profile.user_location)

How to dynamically set field value in views?

I have a model, say 'Article', with a field
published = models.BooleanField(default=True)
and a template with condition:
{% if user.is_staff %}
<li>form.published.label_tag</li>
<li>form.published</li>
{% else %}
<li>form.published.as_hidden</li>
{% endif %}
and I use class-based generic views to add and update for this model.
In this case it is still possible for regular non-staff and malicious user to replace the value of published field.
I think I have to move the condition to views level to prevent this issue, somithing like
class ArticleEdit(UpdateView):
model = Article
form_class = ArticleForm
def form_valid(self, form):
self.object = form.save(commit=False)
if self.request.user.is_staff:
''' How to let the staff change this value? '''
else:
''' How to set previous value? '''
self.object.save()
return HttpResponseRedirect(self.get_success_url())
assuming that I remove this hidden field from template.
I would consider defining two forms, one for staff and one for regular users. You can then override the get_form_class method to select the correct form. If you exclude the published field from the form for non-staff, then they won't be able to change the value.
class ArticleForm(forms.ModelForm):
class Meta:
model = Article
exclude = ('published',)
class ArticleStaffForm(ArticleForm)
class Meta:
model = Article
exclude = ()
class ArticleEdit(UpdateView):
...
def get_form_class(self):
if self.request.user.is_staff:
return ArticleStaffForm
else:
return ArticleForm
you can do something like:
class MyForm(forms.Form):
def __init__(self, user, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
if not user.is_staff:
del self.fields['published']
and then pass the request.user object to the form when initialising it.
WARNING: Untested pseudo code. But this should give you an idea.