Django - __init__() missing 1 required positional argument: 'request' - python

I have the following error:
CompareFormTransporterCompany.__init__() missing 1 required positional argument: 'request'
Here is my form in forms.py:
class CompareFormTransporterCompany(forms.ModelForm):
file = forms.FileField(label="File (CSV, XLSX, XML) ", required=True)
name_transporter = forms.ModelChoiceField(label='Choose transporter', required=True, queryset=Transporter.objects.all())
class Meta:
model = CheckFile
fields = ['file',]
def __init__(self, request, *args, **kwargs):
super().__init__(*args, **kwargs)
self.request = request
def clean(self):
super().clean()
uploaded = parse_csv(self.request.FILES['file'])
In views.py:
class ResultFormView(FormView):
""" View to show results of comparison between two files. """
template_name = 'tool/upload.html'
form_class = CompareFormTransporterCompany
success_url = reverse_lazy('tool:result')
And my model in models.py if needed:
class CheckFile(models.Model):
name = models.CharField(max_length=200, blank=True)
month = models.DateField(blank=True, null=True)
timestamp = models.DateTimeField(blank=True, null=True)
profile = models.CharField('Choix du profil', blank=False, choices=PROFILE_CHOICES, max_length=100, default="Client")
file = models.FileField(blank=True, null=True, upload_to="uploads/", validators=[validate_file_extension])
def __str__(self):
return self.name
class Meta:
verbose_name = "file"
verbose_name_plural = "files"
I have been trying a lot of different solutions but I don't understand the error. Could you please help me?

Thought it came from my form but it came from views.py. I have:
class RequestFormMixin:
"""Mixin to inject the request in the form."""
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs["request"] = self.request
return kwargs
'''
Form result2:
'''
class ResultFormView(RequestFormMixin, FormView):
""" View to show results of comparison between two files. """
template_name = 'tool/upload.html'
form_class = CompareFormTransporterCompany
success_url = reverse_lazy('tool:result')
By changing class ResultFormView(FormView): to class ResultFormView(RequestFormMixin, FormView):, it's working.
Sorry for this question, I'm still pretty new to Django and I didn't give all the informations needed.

Related

Django - How to include all form logic in modelForm and not in a Createview, so that form can be unittested without also testing the view?

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

How to fix missing 1 required positional argument 'request' error?

TypeError at /risk/riskset
get_context_data() missing 1 required positional argument: 'request'.
See code here:
models.py
class RiskSet(models.Model):
name = models.CharField('Risk set', max_length=500, blank=True, default = '')
owner = models.ForeignKey(User, verbose_name = 'owner', on_delete=models.PROTECT, null=True)
risk = models.ForeignKey(Risk, verbose_name = 'risk', on_delete=models.PROTECT, null = True)
parent_risk_set = models.ForeignKey('self', related_name="child_risk_set", on_delete=models.PROTECT, blank=True, null=True)
def __str__(self):
return "{}".format(self.name)
forms.py
class RiskSetForm(forms.ModelForm):
RiskID1 = forms.ModelMultipleChoiceField(queryset=Risk.objects.all(), required=True,
widget=forms.SelectMultiple(attrs={'class': 'select2'}),
label = 'Risk id')
def __init__(self, *args, **kwargs):
super(RiskSetForm, self).__init__(*args, **kwargs)
print(self)
for visible in self.visible_fields():
visible.field.widget.attrs['class'] = 'form-control'
class Meta:
model = RiskSet
fields = ['name', 'owner', 'risk', 'parent_risk_set']
views.py
class RiskSet(FormView, SingleTableMixin):
template_name = "risk/RiskSet.html"
model = RiskSet
form_class = RiskSetForm
def get_context_data(self, request):
form = RiskSetForm(request.POST or None)
if form.is_valid():
form.save()
# Call the base implementation first to get a context
context = super().get_context_data(**kwargs)
# Add in a QuerySet of all the books
context['page'] = 'risk'
return context
Now I get the error:
TypeError at /risk/riskset
get_context_data() missing 1 required positional argument: 'request'
Please help!
get_context_data does not work with a request parameter. You can pass an arbitrary number of parameters, but here your get_context_data will only run if it is called with the request. You access the request with self.request:
class RiskSet(SingleTableMixin, FormView):
template_name = "risk/RiskSet.html"
model = RiskSet
form_class = RiskSetForm
def get_context_data(self, *args, **kwargs):
form = RiskSetForm(self.request.POST or None)
if form.is_valid():
form.save()
# Call the base implementation first to get a context
context = super().get_context_data(*args, **kwargs)
# Add in a QuerySet of all the books
context['page'] = 'risk'
return context
Furthermore it makes no sense to do this in the get_context_data method. A FormView has routines in place for this. It You can probably also work with a CreateView which will remove more boilerplate code, like:
from django.views.generic import CreateView
class RiskSet(SingleTableMixin, CreateView):
template_name = "risk/RiskSet.html"
model = RiskSet
form_class = RiskSetForm
success_url = 'path-to-url-when-form-is-valid'
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
# Add in a QuerySet of all the books
context['page'] = 'risk'
return context

Private messaging system Django

I've been trying to set up a basic private messaging system in Django using the generic CreateView.
I am currently having trouble with the "Receiver"/"To" field in my form. I tried to make it so it was a drop down field with the options being followers of the logged-in user.
Currently, the field is populating with the correct usernames (in this case, "testuser1") but it is throwing an error saying this field needs to be populated with an instance of the User object.
ValueError: Cannot assign "'testuser1'": "Message.reciever" must be a "User" instance.
Is there a way to have the form pass in the object of the username that is selected?
Model:
class Message(models.Model):
sender = models.ForeignKey(User, on_delete=models.CASCADE, related_name="sender")
reciever = models.ForeignKey(User, on_delete=models.CASCADE, related_name="reciever")
subject = models.CharField(max_length=128, default="-")
content = models.TextField()
send_date = models.DateTimeField(default=timezone.now)
User Relationships Model:
class UserRelationships(models.Model):
user_id = models.ForeignKey(User, on_delete=models.CASCADE, related_name="following")
following_user_id = models.ForeignKey(User, on_delete=models.CASCADE, related_name="followers")
created = models.DateTimeField(auto_now_add=True)
UPDATED Form:
class MessageCreateForm(forms.ModelForm):
class Meta:
model = Message
fields = ['sender', 'reciever', 'subject', 'content']
widgets = {'sender': forms.HiddenInput()}
def __init__(self, *args, **kwargs):
user = kwargs.pop('user')
follower_objects = kwargs.pop('follower_objects')
super(MessageCreateForm, self).__init__(*args, **kwargs)
self.fields['reciever'] = RecieverModelChoiceField(queryset=User.objects.filter(username__in=follower_objects))
View:
class MessageCreateView(LoginRequiredMixin, CreateView):
model = Message
template_name = 'message/compose.html'
form_class = MessageCreateForm
def get_initial(self):
initial = super().get_initial()
initial['sender'] = self.request.user
return initial
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
user = self.request.user
followers = user.followers.values_list('user_id', flat=True)
follower_objects = []
kwargs['user'] = self.request.user
kwargs['follower_objects'] = follower_objects
for id in followers:
follower = User.objects.get(id=id)
follower_objects.append(follower)
return kwargs
def form_valid(self, form):
form.instance.user = self.request.user
return super().form_valid(form)
You have to use forms.ModelChoiceField instead of forms.ChoiceField
ForeignKey (model) > ModelChoiceField (form) - Default widget: Select
ModelChoiceField has attribute queryset.
You can filter field reciever.queryset directly in MessageCreateForm.__init__ method.
def __init__(self, *args, **kwargs):
user = kwargs.pop('user')
super(MessageCreateForm, self).__init__(*args, **kwargs)
self.fields['reciever'].queryset = user.followers
UPDATE:
You can set a custom ModelChoiceField that will return any label you want (more info).
from django.forms import ModelChoiceField
class RecieverModelChoiceField(ModelChoiceField):
def label_from_instance(self, obj):
return obj.username
or
def __init__(self, *args, **kwargs):
....
self.fields['reciever'].label_from_instance = lambda obj: "%s" % obj.username

Run all the code in a Django ModelForm __init__() method after an invalid form submission

Based on this model:
class Booking(models.Model):
"""
Model Booking with foreign key to models Session and Bundle.
"""
session = models.ForeignKey(verbose_name=_('Session'), to=Session, default=None, null=False, blank=False)
bundle = models.ForeignKey(verbose_name=_('Bundle'), to=Bundle, default=None, null=True, blank=True)
price = models.DecimalField(verbose_name=_('Price'), max_digits=10, decimal_places=2,
default=None, null=False, blank=False)
name = models.CharField(verbose_name=_('Name'), max_length=100, default=None, null=False, blank=False)
email = models.EmailField(verbose_name=_('Email'), null=True, blank=True)
phone_number = models.CharField(verbose_name=_('Phone Number'), max_length=30, null=True, blank=True)
def __str__(self):
return "%s #%s" % (self.name, self.bundle if self.bundle else self.session)
I have the following ModelForm:
class BookingForm(forms.ModelForm):
name = forms.CharField(max_length=100, required=True)
class Meta:
model = Booking
fields = ['session','bundle', 'name', 'email', 'phone_number']
widgets = {
'bundle': forms.RadioSelect,
'session': forms.HiddenInput,
}
def __init__(self, *args, **kwargs):
session_pk = kwargs.pop('session', False)
super(BookingForm, self).__init__(*args, **kwargs)
if session_pk is not False:
session = Session.objects.filter(pk=session_pk).first()
if session:
self.fields['session'].initial = session
if not session or not session.is_bookable:
raise Exception("Session is not available")
elif session.bundles:
self.fields['bundle'].widget.attrs['choices'] = session.bundles
self.fields['bundle'].initial = session.bundles[0] if len(session.bundles) == 1 else None
self.fields['bundle'].empty_label = None
else:
del self.fields['bundle']
Using this Class-based CreateView:
class BookingCreateView(generic.CreateView):
template_name = 'events/booking_form.html'
form_class = BookingForm
def get_form_kwargs(self):
"""
Extended method so we can pass in the Session object's pk.
"""
kwargs = super(BookingCreateView, self).get_form_kwargs()
if self.request.method == 'GET':
session_kwarg = {
'session': self.kwargs.get('pk', None),
}
kwargs.update(session_kwarg)
return kwargs
I only want the bundle field to be displayed if the session field has associated bundles, as the code shows. It works when I first render the template. However if I post the form with invalid fields, the bundle field will be rendered along with the other fields.
Question: How can I make the logic inside my init() be executed after the form has been submitted incorrectly? Preferentially without having to recur to JavaScript.
So I've figured out how to solve my problem:
In my ModelForm I changed the signature of the __init__ method to pass in my session value outside of the kwargs:
def __init__(self, session=None, *args, **kwargs):
session_pk = session
super(BookingForm, self).__init__(*args, **kwargs)
And then, in my CreateView method get_form_kwargs(self), I was checking the condition:
if self.request.method == 'GET'
It happened that I wasn't passing any session in the form's kwargs when request.method == 'POST'. Problem solved!

Automatically create a record in another model after using CreateView

I have two models (OK 3 models since AssignedAsset is a subclass of Asset), one that tracks assets and another that tracks the history of owners for that asset. When I create a new asset using CreatView I would like to automatically have it create a History record as well.
models.py
class Asset(models.Model):
make = models.CharField(max_length=100)
model = models.CharField(max_length=100)
serial_number = models.CharField(max_length=100)
po = models.ForeignKey('purchaseorders.PurchaseOrder', default=None, blank=True, null=True)
location = models.ForeignKey('locations.Plant')
slug = models.SlugField(blank=True, unique=True)
def __str__(self):
return self.slug
def save(self):
forslug = "{0.make}-{0.model}-{0.serial_number}".format(self)
self.slug = slugify(forslug)
super(Asset, self).save()
class AssignedAsset(Asset):
user = models.ForeignKey(User)
def __str__(self):
return self.slug
class AssignedHistory(models.Model):
assset = models.ForeignKey('Asset')
user = models.ForeignKey(User)
date = models.DateField()
slug = models.SlugField(blank=True, unique=True)
def __str__(self):
return self.slug
def save(self):
forslug = "{0.asset}-{0.date}".format(self)
self.slug = slugify(forslug)
super(AssignedHistory, self).save()
Here is my view.
class NewAssignedAsset(CreateView):
form_class = AssignedAssetForm
template_name = 'createassignedasset.html'
success_url = '/assets'
And my forms.py
class AssignedAssetForm(forms.ModelForm):
class Meta:
model = AssignedAsset
fields = ['make', 'model', 'serial_number', 'location', 'user', 'po']
def __init__(self, *args, **kwargs):
super(AssignedAssetForm, self).__init__(*args, **kwargs)
#Filter out PO's that have packingslips (otherwise you will quickly have a ridicously big drop-down of every PO in the system)
self.fields['po'] = forms.ModelChoiceField(required=False, queryset=PurchaseOrder.objects.filter(packing_slip=''))
I thought maybe I could have it create the history when it gets the success URL, so I tried this in my view:
import time
def today():
return time.strftime ("%m/%d/%Y")
class NewAssignedAsset(CreateView):
form_class = AssignedAssetForm
template_name = 'createassignedasset.html'
def get_success_url(self):
history = AssignedHistory.objects.create(assset=self.object, user=self.object.user, date=today())
return '/assets'
But this throws a TypeError:
save() got an unexpected keyword argument 'force_insert'
Anything that would point me in the right direction would be appreciated.
You can do it at multiple levels(DB level, form level).
In your case, I'll say you just need to override the save() of your AssignedAssetForm. (Assuming you set user in context of form)
def save(self, *args, **kwargs):
assigned_asset = super(AssignedAssetForm, self).save(*args, **kwargs)
user = self.context.get(u'user')
if user:
assigned_asset_history = AssignedHistory(asset=assigned_asset, user=user, date=datetime.date.today())
assigned_asset_history.save()
return assigned_asset
** I am not sure about the context part, you may have to look into how to use user in form.
You should write your Asset.save() and AssignedHistory.save() as:
def save(self, **kwargs):
...
super(YourModel, self).save(**kwargs)
...
Note the **kwargs. They allow you to accept optional parameters (and a Model.save() has a few).

Categories