UPDATE: Dynamic MultipleChoiceField changes instances to strings - python

I have a django form that has a multiple choice field. The field should be dynamic, that is, only the records associated with the user who is currently logged in should be displayed. I've managed to put this together so far;
forms.py
class myForm(forms.ModelForm):
def __init__(self, someUser, *args, **kwargs):
super(myForm, self).__init__(*args, **kwargs)
someRecords = models.SomeModel.objects.filter(someUser = someUser)
#The line above gets records associated with a specific user
displayNames = []
for i in someRecords:
displayNames.append((i.someField, i.someOtherField + ' ' + i.someOtherField2))
#I am basically making a list of tuples, containing a field and a concatnation of two other fields. The latter will be what is displayed in the select box
self.fields['theSelectField'] = forms.ChoiceField(choices = displayNames)
class Meta:
#I defined model, fields and labels here
views.py
def myFormPage(request):
someUser = request.user.someextensionofuser.someUser
form = forms.myForm(someUser)
context = {'form': form}
if request.method == 'POST':
form = forms.myForm(someUser, data = request.POST)
if form.is_valid():
#Do Stuff if form is valid. However,this stuff doesn't get done, the page refreshes instead
So I've managed to make the select options dynamic. However, now I can't submit data.
EDIT: One of the comments helped me solve the previously stated problem. I've updated the views.py code. However, now I'm running into this error;
Cannot assign "'someString'": "someModel.someField" must be a
"someForeignModel" instance
The option values seem to be strings instead of references to objects. How do I solve this?

This limits the possible options of your select field:
self.fields['theSelectField'].queryset = SomeModel.objects.filter(someUser = someUser)
In your views you might want to use a Class Based View, because it handles a lot of stuff automatically and saves you time. Take a look here: https://ccbv.co.uk/

I firgured it out. Since my main problem was with how the options are displayed to a user, I decided to go with changing my str method in models.py to;
class someModel(models.Model):
#my fields
def __str__(self):
return self.someField + ' ' + self.someOtherField
Then in my forms.py, I went with #dmoe's answer;
self.fields['theSelectField'] = forms.ModelChoiceField(queryset = models.SomeModel.objects.filter(someUser = someUser))
So now both problems are solved. My options have custom labels, and I can submit my data without running into valueError.

Related

How to save multiple objects in Django models at once

What I'm trying to do is saving multiple scraped data(actor names) in my Actor table inside Django models.
So far I've written the for loop below to achieve this goal but it only saves the last object.
class Actor(models.Model):
name = models.CharField(max_length=50)
url = models.URLField()
def __str__(self):
return self.name
def save(self, *args, **kwargs):
i = 0
for num in range(5):
title, year, actor_list = scraper(self.url)
self.name = actor_list[i]
super().save(*args, **kwargs)
i += 1
I get the URL from users within a form, then save the form and send it to my models and the scraping begins.
I scrape 5 actor names inside the scraper() function and append them to actor_list.
But when I try to save the actor_list under the save function, it only saves the 5th actor which I think overwrites the previously saved objects.
Is there something wrong with my for loop? or should I completely change my approach for this purpose?
I'd also prefer to save the objects using Actor.objects.get_or_create() to skip the already existing objects, but I don't know how and where.
I would appreciate it if someone could help me with this.
Yes, you should use a completely different approach. A Model is just a code representation of your database table.
You are also executing the same scraping 5 times on the same url, but that is another point.
Doing scraping and creating multiple actors should not be done within the model. Bear in mind that the model gets instantiated as an actor object (a single record in the actor table), so every time you do a super().save() it overwrites the same record.
The standard way is to do it in the view you use to print the form and have that form submit to its own URL (i.e. the form action should point to the same view you use to print it).
Something like this:
forms.py
from django import forms
class MyForm(forms.Form):
url = forms.CharField(label='Your name', max_length=255)
views.py
from my_app.forms import MyForm
from my_app.models import Actor
from django.shortcuts import render
def my_view(request):
if request.method == "POST":
form = MyForm(request.POST)
if form.is_valid():
title, year, actor_list = scraper(form.cleaned_data['url'])
for actor in actor_list:
# Here you are creating new instances of Actor (new records)
# at each iteration, unless they already exist.
Actor.objects.get_or_create(name=actor)
else:
form = MyForm()
return render(request, 'your_template.html', {'form': form})
The save() method acts on a single Actor object. It's wrong to try to save multiple actors there.
Do this in a view:
title, year, actor_list = scraper(self.url)
for i in range(5):
actor = Actor()
actor.name = actor_list[i]
actor.save()
And delete the save() method in the model

pass parameter from CreateView to template (Django)

I've seen a similar question, alas it has not been answered.
I have an app that features Entries (like blog entries) which include a part called SubEntry. I want the users to be able to report SubEntries (i.e. press the button 'report', fill some fields and the application sends an email to admins, saving the report in db is nice to have):
The flow should be like that: at the view EntryDetails (url: /entry/entry-title/) the user may click on the SubEntry part. The modal opens and the subentry is visualized in the modal as enlarged, with a button/link underneath 'Report the SubEntry'. Then it's possible to click on the 'Report the SubEntry' button and two fields appear - reason of reporting and contact detail of the reporter (here I am just toggling the visibility of the fields). I manage to display the form (with get overriden - overriding get_form_kwargs causes the error No Entry with that title) but either the Entry or its attributes are not displayed...
My questions are:
1) is creating a model for Reporting (ReportSubEntry) a decent approach?
2) I can't seem to pass the needed variable (an Entry object that is to be a ForeignKey for a SubEntry object that is being created) from CreateReport view to the report_subentry.html.
any thoughts, advice? Python 3.5, Django 1.10
models.py:
class ReportSubentry(models.Model):
Entry = models.ForeignKey('Entry')
details = models.CharField(max_length=100)
contact = models.EmailField()
forms.py:
class ReportEntryForm(forms.ModelForm):
class Meta:
model = ReportSubEntry
fields = ['details', 'contact', 'project']
views.py:
class CreateReport(CreateView):
model = ReportSubEntry
form_class = ReportSubEntryForm
template_name = 'understand/report_subentry.html'
# tried two methods to pass the variables:
def get(self, request, *args, **kwargs):
self.object = None
title = kwargs.get('title')
kwargs['entry'] = get_object_or_404(Entry, title=title)
return super(CreateReport, self).get(request, **kwargs)
def get_form_kwargs(self, **kwargs):
title = kwargs.get('title')
kwargs['entry'] = get_object_or_404(Entry, title=title)
return kwargs
The current model that you are using ReportSubEntry is perfect and there is no need to change it.
In your forms.py ReportEntryForm you have to use relatedfields to be able to correctly serialize the data. There is no need to override anything. When user clicks on report the sub entry you have to pass the pk of Entry model as it is required to know which entry is reported. I am assuming that since you are successfully displaying the entries pk of those are present. When you receive the pk with other two fields you get the corresponding entry for pk and then pass the object to ReportSubentry.objects.create method.
The reportentry form should not contain foreign key. You have two choices for that. First is remove that field and pass the pk of entry from frontend using ajax calls or use javascript to add a disabled input field which contains pk of entry when user clicks on report subentry.
Ok, so I've solved this issue.
The only solution that worked for me was overriding the get method of the ReportSubentry without calling the get method of the superclass:
def get(self, request, *args, **kwargs):
self.object = None
title = kwargs.get('title')
entry = get_object_or_404(Entry, title=title)
context_data = self.get_context_data()
context_data.update(entry=entry)
return self.render_to_response(context_data)
Please feel free to discuss it.

How to access field names of a ModelForm in Django?

Just to be clear, I'm asking about accessing the fields in views.py
I want to add extra data into the form before it is validated (because it's a required field), and another answer on stackexchange seems to imply I have to create a new form to do so.
Right now my code look something like this:
if request.method == 'POST':
# create a form instance and populate it with data from the request:
form = TestForm(request.POST)
data = {}
for ---:
---add to data---
comp = Component.objects.get(name = path)
data['component'] = comp.id
form = TestForm(data)
if form.is_valid():
test = form.save(commit = 'false')
test.save()
return submitTest(request, var)
How could I fill in the parts with dashes?
This is the wrong thing to do. There is no reason to add in a required field programmatically; if you know the value of the field already, there is no reason to include it on the form at all.
I don't know what you mean about having to create another form; instead you should explicitly exclude that field, in the form's Meta class, and set the value on the test object before calling test.save().
Edit after comment I still don't really understand why you have data coming from two separate places, but maybe you should combine them before passing to the form:
data = request.POST.copy()
data['myvalue'] = 'myvalue'
form = MyForm(data)
I figured out what I was doing wrong. In my TestForm modelform I didn't include the 'component' field because I didn't want it to show up on the form. As a result, the 'component' data was being cleaned out during form validation even if I inserted it into the form correctly. So to solve this I just added 'component' into the fields to display, and to hide it on the form I added this line
widgets = {'component': HiddenInput()}
to the TestForm class in forms.py.

Django admin error in many-to-many relationship

For example.
class One(models.Model):
text=models.CharField(max_length=100)
class Two(models.Model):
test = models.Integer()
many = models.ManyToManyField(One, blank=True)
When I try save my object in admin panel, I take error such as:
"'Two' instance needs to have a primary key value before a many-to-many relationship can be used."
I use django 1.3. I tried add AutoField to Two class, but it's not work too.
This is my code.
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render_to_response, redirect
from django.template import RequestContext
from django.core.urlresolvers import reverse
from project.foo.forms import FooForm
from project.foo.models import Foo
from project.fooTwo.views import fooTwoView
def foo(request, template_name="foo_form.html"):
if request.method == 'POST':
form = FooForm(data=request.POST)
if form.is_valid():
foo = Foo()
foo.name = request.POST.get("name")
foo.count_people = request.POST.get("count_people")
foo.date_time = request.POST.get("date_time")
foo.save()
return fooTwoView(request)
else:
form = FooForm()
return render_to_response(template_name, RequestContext(request, {
"form": form,
}))
P.S. I find my fail. It is in model. I used many-to-many in save method. I add checking before using, but it's not help.
class Foo(models.Model):
name = models.CharField(max_length=100, null=False, blank=False)
count_people = models.PositiveSmallIntegerField()
menu = models.ManyToManyField(Product, blank=True, null=True)
count_people = models.Integer()
full_cost = models.IntegerField(blank=True)
def save(self, *args, **kwargs):
if(hasattr(self,'menu')):
self.full_cost = self.calculate_full_cost()
super(Foo, self).save(*args, **kwargs)
def calculate_full_cost(self):
cost_from_products = sum([product.price for product in self.menu.all()])
percent = cost_from_products * 0.1
return cost_from_products + percent
I try hack in save method such as
if(hasattr(self,Two)):
self.full_cost = self.calculate_full_cost()
This is help me, but i dont think that is the django way. What is interesting, that is without this checking admin panel show error, but create object. Now, if i select item from Two and save, my object does not have full_cost, but when i view my object, admin panel remember my choice and show me my Two item, what i select... I dont know why.
How do i save this?
There are quite a few problems with your code. The most obvious one are
1/ in your view, using a form for user inputs validation/sanitization/conversion then ignoring the santized/converted data and getting unsanitized inputs directly from the request. Use form.cleaned_data instead of request.POST to get your data, or even better use a ModelForm which will take care of creating a fully populated Foo instance for you.
2/ there's NO implicit "this" (or "self" or whatever) pointer in Python methods, you have to explicitely use "self" to get at the instance attributes. Here's what your model's "save" method really do:
def save(self, *args, **kwargs):
# test the truth value of the builtin "id" function
if(id):
# create a local variable "full_cost"
full_cost = self.calculate_full_cost()
# call on super with a wrong base class
super(Banquet, self).save(*args, **kwargs)
# and exit, discarding the value of "full_cost"
Now with regard to your question: Foo.save is obviously not the right place to compute someting based on m2m related objects. Either write a distinct method that run the computation AND update Foo AND save it and call it after the m2m are saved (hint : a ModelForm will take care of saveing the m2m related objects for you), or just use the m2m_changed signal.
This being said, I strongly suggest you spend a few hours learning Python and Django - it will save you a lot of time.
Why not use "OneToOneField" instead of Many-to-Many

Using FormWizard and saving the forms data in between before the completion of the whole process?

I am using FormWizard to complete a set of operation in my app, I have two models Employee and Person, Employee class inherits Person, and all the fields of Person are available for Employee object.
Now I am creating a set of forms using FormWizard, I just wanted to know that. If a user starts entering the data in the forms and fills upto 2 forms out of 4 and is willing to fill the rest of the forms afterwards. So is this possible that the data for the two forms which he filled can be saved in the database.
And the next time he comes can complete the operation form the 3rd form.
If anyone knows that then plz help me out, it would be a great help. Thank You!
what you can do is every step, save out the form state to some serialised object in db ForeignKeyed to the user.
then when hooking up the formwizard, wrap the formwizard view in a custom view which checks if the user has a saved form and if so deserialises and redirects to the appropriate step.
Edit: seems formwizard saves state in POST. only need to save postdata.
models.py:
class SavedForm(Model):
user = ForeignKey(User)
postdata = TextField()
views.py:
import pickle
class MyWizard(FormWizard):
def done(self, request, form_list):
SavedForm.objects.get(user=request.user).delete() # clear state!!
return render_to_response('done.html',)
formwizard = MyWizard([Form1, Form2]) <- class name, not instance name
def formwizard_proxy(request, step):
if not request.POST: #if first visit, get stored data
try:
prev_data = SavedForm.objects.get(user=request.user)
request.POST = pickle.loads(prev_data.postdata)
except:
pass
else: # otherwise save statet:
try:
data = SavedForm.objects.get(user=request.user)
except:
data = SavedForm(user=request.user)
data.postdata=pickle.dumps(request.POST)
data.save()
return formwizard(request)
edit: changed formwizard constructor

Categories

Resources