How do you pass request data to Forms in Django? - django

here I am using model forms and trying to make my placeholder dynamic.
my approach is to take request data, pass it into widgets with f string.
what I am trying to achieve is
{'placeholder': f"commenting as {request.user.username}"}
HERE IS MY CODE.
class CommentForm(ModelForm):
class Meta:
model = Comment
fields = ("body",)
widgets = {
"body": forms.TextInput(
attrs={
"placeholder": "Enter your comment",
"class": "comment-form-text",
}
),
}
labels = {
"body": "",
}

This is how I usually pass the request object in a form.
Note: all you need is the CommentForm.__init__ and calling it with CommentForm(request.POST, request=request)
I just added the custom save, but commented it out, to show you can also access it there and do some cool things! :-)
forms.py
class CommentForm(ModelForm):
class Meta:
model = Comment
fields = ("body",)
widgets = {
"body": forms.TextInput(
attrs={
"class": "comment-form-text",
}
),
}
labels = {
"body": "",
}
def __init__(self, *args, **kwargs):
# # Keeping track of if it's an edit form or not ( Not Required, but handy )
# self.is_edit = True if 'instance' in kwargs else False
# Store Request Object
self.request = kwargs.pop('request') if 'request' in kwargs else None
super(CommentForm, self).__init__(*args, **kwargs)
# You can add *Any* custom attribute here to any field
self.fields['body'].widget.attrs={'placeholder': 'commenting as {0}'.format(self.request.user.username)}
# # Just showing that you can also use it in a Custom Save Method :-)
# def save(self, commit=True):
# obj = super(CommentForm, self).save(commit=False)
#
# # Note: Keeping track of **if** it's an edit so we don't re-add to the field!
# if not self.is_edit:
# # Use Request to fill a field (New)
# obj.creator = request.user
# else:
# # Use request to fill a field (edit)
# obj.last_editor = request.user
views.py
def commentformview(request):
form = CommentForm(data=request.POST or None, request=request)
if request.method == 'POST':
if form.is_valid():
form.save()
# redirect
data = {
'form': form,
}
return render(request, 'commentform.html', data)

Related

Django testing Forms.FileField

I'm trying to test my Comment Form with FileField.
class CommentForm(forms.ModelForm):
files = forms.FileField(
widget=ClearableFileInput(
attrs={"multiple": True, "class": "form-control", "id": "formFile"}
),
required=False,
label=_("Files"),
)
def __init__(self, *args, **kwargs):
self.ticket = None
super().__init__(*args, **kwargs)
class Meta:
model = Comment
fields = ["body", "files"]
labels = {"body": _("Comment body")}
def save(self, commit=True, new_status=None):
instance = super(CommentForm, self).save(commit=False)
if commit:
instance.save()
file_list = []
for file in self.files.getlist("files") or self.initial.get("files") or []:
file_instance = self.save_attachment(file=file)
file_list.append(file_instance)
if len(file_list) > 0:
async_task(add_attachments, file_list, instance.ticket.get_pk())
Writing test without attachment upload is easy, but problem starts when I want to test my form with files. I could not test whether async_task is called, and when I try to mock files and pass them to the Form, the attachment object does not apper in test database
Here is my test:
#patch("myapp.forms.comments.async_task", autospec=True)
def test_create_comment_with_attachment(self, async_task):
file_mock = mock.MagicMock(spec=File)
file_mockname = "test_name.pdf"
form = CommentForm(
data={
"body": "Test comment",
"files": mock.MagicMock(file_mock)
}
)
form.ticket = self.ticket
self.assertTrue(form.is_valid())
form.save()
attachment = Attachment.objects.last()
print(attachment)
The result of printing attachment is None, while I expected my file_mock to appear.
Can anyone help my with testing attachment upload?

Django Pass Request Data to Forms.py

The scenario;
We got a from with fields and inside form there is a combobox, it fills with items.
We have tenancy and every user got TenantID so when A1 user(tenantid 1) calls create form, we need to filter that combobox to filter only A1 UserItems with using Query Filtering.
Similarly for other tenants.
How can I pass that dynamic tenantid.
Btw for every user tenantid stored in abstracted class django core USER- added new field tenantid.
Any advice Im open for it, thank you for your attention.
State: Solved !
Forms.py
class ItemForm(forms.ModelForm):
class Meta:
model = Items
fields = ('id', 'item', 'start', 'end')
widgets = {
'start': DateTimePickerInput(format='%Y-%m-%d %H:%M'),
'end': DateTimePickerInput(format='%Y-%m-%d %H:%M'),
}
def __init__(self, *args, **kwargs):
super(ItemForm, self).__init__(*args, **kwargs)
self.fields['item'].queryset = Items.objects.filter(tenantid=int(User.tenantid))
views.py
#login_required()
def create_item_record(request):
if request.method == 'POST':
form = ItemForm(request.POST)
if request.method == 'GET':
tenantidX = request.user.tenantid
form = ItemForm()
return save_item_form(request, form, 'items_create_partial.html')
Just pass user from request to your form:
class ItemForm(forms.ModelForm):
class Meta:
model = Items
fields = ('id', 'item', 'start', 'end')
widgets = {
'start': DateTimePickerInput(format='%Y-%m-%d %H:%M'),
'end': DateTimePickerInput(format='%Y-%m-%d %H:%M'),
}
def __init__(self, user, *args, **kwargs):
super(ItemForm, self).__init__(*args, **kwargs)
self.fields['item'].queryset = Items.objects.filter(tenantid=int(user.tenantid))
#login_required()
def create_item_record(request):
if request.method == 'POST':
form = ItemForm(request.user, request.POST)
if request.method == 'GET':
form = ItemForm(request.user)
return save_item_form(request, form, 'items_create_partial.html')
the best and easy way of getting current request with using "django -crum" https://pypi.org/project/django-crum/ .
pip install django-crum
after that add to settings.py
# settings.py
MIDDLEWARE_CLASSES = (
'crum.CurrentRequestUserMiddleware',
...
)
include lib
from crum import get_current_request
request = get_current_request()
Then you can reach active request inside with request.user.tenantid

Django How to pass instance to view

I can't find out how to do this, I have a list view, that when you click on one of the list objects, it takes you to an update page, but I can't work out how you pass the instance so to it so that and data posted goes to that instance on the database. CBV do this automatically in a hidden black box way, and I can't see how it is done for a function based view.
Model
class Project(models.Model):
date_published = models.DateTimeField(auto_now_add=True)
title = models.CharField(max_length=128, unique=True)
slug = models.SlugField(max_length=64)
def save(self, *args, **kwargs):
if not self.id:
self.slug = slugify(self.title)
super(Project, self).save(*args, **kwargs)
def __str__(self):
return self.title
Form
class ProjectUpdateForm(forms.ModelForm):
class Meta:
model = Update
fields = [
'project',
'category',
'update'
]
View
def project_update_view(request, slug):
obj = Project.objects.get(slug=slug)
form = ProjectUpdateForm(request.POST or None)
if form.is_valid():
form.save()
context = {
"form": form,
"object": obj
}
return render(request, 'project_portal/project_update.html', context)
url:
path('<slug:slug>/update/', project_update_view, name='project-update'),
So I want to be able to do away with the 'project' field in the Form because the user is already looking at that instance he shouldn't have to then pick it in the form.
Remove the project in the field, and set in in the view, like:
class ProjectUpdateForm(forms.ModelForm):
class Meta:
model = Update
fields = [
# 'project',
'category',
'update'
]
In the view, you can then set the project attribute of the instance manually:
def project_update_view(request, slug):
obj = Project.objects.get(slug=slug)
if request.method == 'POST':
form = ProjectUpdateForm(request.POST)
form.instance.project = obj
if form.is_valid():
form.save()
return redirect('success-url')
else:
form = ProjectUpdateForm()
context = {
"form": form,
"object": obj
}
return render(request, 'project_portal/project_update.html', context)
Some extra notes:
do not use request.POST or None, since a POST request can be valid and have no POST parameters;
in case the POST is successful, you should implement a Post/Redirect/Get pattern [wiki].

Django CreateView - How to show a related field (FK) like integer instead of select?

Hi (sorry for my bad english), i'm trying to show a form using a number input instead a select in a FK Relation on django model, but i cant make it.
The thing is that i need to fill the ID of the product manually writing the id in a input box, but django automatically makes the select form with all the products!
this is what i have in forms.py
class PurchaseForm(forms.ModelForm):
class Meta:
model = Purchase
fields = [
'date',
'supplier',
'warehouse',
]
widgets = {
'date': forms.DateInput(attrs={
'type': 'date',
}),
}
class PurchaseItemForm(forms.ModelForm):
class Meta:
model = PurchaseItem
fields = [
'product',
'quantity',
'net_purchase_price',
'sell_price',
]
widgets = {
'product': forms.NumberInput(),
}
PurchaseFormSet = inlineformset_factory(
Purchase,
PurchaseItem,
fields='__all__',
extra = 10,
)
this is my view
class PruchaseCreateView(CreateView):
template_name = 'purchases/create_purchase.html'
form_class = PurchaseForm
def get_context_data(self, **kwargs):
context = super(PruchaseCreateView, self).get_context_data(**kwargs)
if self.request.POST:
context['formset'] = PurchaseFormSet(self.request.POST)
else:
context['formset'] = PurchaseFormSet()
return context
def form_valid(self, form):
context = self.get_context_data()
formset = context['formset']
if formset.is_valid():
self.object = form.save()
formset.instance = self.object
formset.save()
return redirect(self.object.get_absolute_url())
else:
return self.render_to_response(self.get_context_data(form=form))
i try with the widget NumberInput but nothing!!! any idea?
You need to include the custom form when you call inlineformset_factory:
PurchaseFormSet = inlineformset_factory(
Purchase,
PurchaseItem,
form=PurchaseItemForm
fields='__all__',
extra = 10,
)

django form set current login user

#login_required
def post_review(request):
if request.method == 'POST':
formset = ReviewForm(request.POST)
if formset.is_valid():
formset.save(commit=False)
#formset.author = User.objects.get(pk=int(request.user.id))
formset.pub_date = datetime.datetime.now
formset.save()
return HttpResponseRedirect(reverse(review_index))
else:
formset = ReviewForm()
return render_to_response("review/post_review.html",
{"formset": formset}, context_instance=RequestContext(request),
)
I have this view, I want to auto set the current logged-in user in my review form author field. But I dont know how. Any ideas/hint pls?
Below is my form:
class ReviewForm(ModelForm):
class Meta:
model = Review
fields = ('title','category', 'body', )
widgets = {
'body': Textarea(attrs={'cols': 60, 'rows': 20}),
}
I've always done this by accepting a new kwarg in my form's __init__, and saving the value until save-time.
class ReviewForm(ModelForm):
class Meta:
model = Review
fields = ('title','category', 'body', )
widgets = {
'body': Textarea(attrs={'cols': 60, 'rows': 20}),
}
def __init__(self, *args, **kwargs):
self._user = kwargs.pop('user')
super(ReviewForm, self).__init__(*args, **kwargs)
def save(self, commit=True):
inst = super(ReviewForm, self).save(commit=False)
inst.author = self._user
if commit:
inst.save()
self.save_m2m()
return inst
And then in my view:
def post_review(request):
# ... snip ...
if request.method == 'POST'
form = ReviewForm(request.POST, user=request.user)
if form.is_valid():
form.save()
return HttpResponseRedirect('/thanks/') #or whatever the url
else:
# Don't forget to add user argument
form = ReviewForm(user=request.user)
# ... snip ...
If Review.author isn't a required field, you can add a second value to the kwargs.pop call to set a default, like None. Otherwise, if the user kwarg isn't provided, it'll raise an error, effectively making it a required argument.
As an alternative solution, in Django 2+ using a form view - such as a CreateView or FormView, I can simply pass the self.request.user to my pre-saved form model:
class AppCreateView(CreateView):
model = models.App
fields = ['name']
success_url = '/thanks/'
def form_valid(self, form):
app_model = form.save(commit=False)
app_model.author = self.request.user
# app_model.user = User.objects.get(user=self.request.user) # Or explicit model
app_model.save()
return super().form_valid(form)
I agree the class based view is not important here. The important line is app_model.author = self.request.user.
The model is not special:
from django.db import models
from django.contrib.auth.models import User
class App(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE)
name = models.CharField(max_length=255, help_text="Arbitrary name")
created = models.DateTimeField(auto_now_add=True, max_length=255)
I have a formset mixin which lets you pass extra arguments to the generated forms.
Just add the mixin as the first base class, set a dictionary named "form_kwargs" as a class attribute to describe the
arguments to pass.
from django.forms.formsets import BaseFormSet
class BaseKwargsFormSet(BaseFormSet):
"""
A formset mix-in to allow keyword arguments to be passed to constructed forms
For model_formsets, derive from this model *first* because django's formsets
can't grok the extra arguments.
To use, specify a dictionary with the kwargs & default values as an attribute
named "form_kwargs" on the formset base class.
example:
class BaseUserModelFormset (BaseKwargsFormSet, BaseModelFormSet):
form_kwargs = { 'user': None }
UserFormset = modelformset_factory (usermodel, form=userform,
formset=BaseUserModelFormset)
formset = UserFormset (request.POST or None, user=request.user)
"""
def __init__(self, *args, **kwargs):
form_kwargs = getattr(self, 'form_kwargs', {})
self.form_kwargs = dict((k, kwargs.pop(k, v)) for k, v in form_kwargs.items())
super(BaseKwargsFormSet, self).__init__(*args, **kwargs)
def _construct_form(self, index, **kwargs):
kwargs.update(**self.form_kwargs)
return super(BaseKwargsFormSet, self)._construct_form(index, **kwargs)