Add extra information after submitting form - django

I have the following structure:
class FeatureType(models.Model):
type = models.CharField(max_length=20)
def __unicode__(self):
return self.type
class Feature(models.Model):
name = models.CharField(max_length=50)
type = models.ForeignKey(FeatureType)
premium = models.BooleanField("Premium Feature", default=False)
def __unicode__(self):
return self.name
What I want to do is when the user is adding a vehicle (the app is for a vehicle inventory), there's a large textbox that will be used to get a list of features from another website by the user copying and pasting them there.
I'm doing fine so far, but what I don't know how to do is, for each feature that's parsed from the textbox, the user will be able to set the type this feature belongs to. Here's my code so far:
vehicle_form = VehicleForm(request.POST or None)
photos = PhotosFormSet(request.POST or None, request.FILES or None)
features = FeaturesForm(request.POST or None)
if vehicle_form.is_valid() and photos.is_valid() and features.is_valid():
vehicle = vehicle_form.save(commit=False)
vehicle.save()
if features.cleaned_data['external']:
external_set = features.cleaned_data['external'].split('\r\n')
for feature in external_set:
#set featuretype somehow
vehicle_feature, created = Feature.objects.get_or_create(name=feature
, type = featuretype)
feature = vehicle.features.add(vehicle_feature)
photos = PhotosFormSet(request.POST, request.FILES, instance=vehicle)
photos.save()
So how can I enable choosing a feature type for each feature?
What I want to do, ideally, is to have existing features matched to the record...for those features that don't exist, the user will be redirected to another view that will enable him to set the type for each new feature and subsequently add it to the record.
UPDATE 1
I've modified the code a bit to look like this:
if features.cleaned_data['external']:
new_features = []
external_set = features.cleaned_data['external'].split('\r\n')
for feature in external_set:
try:
vehicle_feature = Feature.objects.get(external_name = feature)
feature = vehicle.features.add(vehicle_feature)
except Feature.DoesNotExist:
new_features.append(feature)
This takes care of getting the existing features....what I want to do now is to have all the features in new_features passed onto another form after the user saves the current record, where the user will enter further details for each feature.

In your FeaturesForm you can add something like:
feature_type = forms.ChoiceField(label="Feature Type",
choices=FeatureType.objects.values_list('id', 'type'))

Related

(Flask) WTForm gets rendered with wrong values

I am currently working on a web-application based "database-explorer" for a university project.
Basically I create a site for each relation of the database, where the user can view the data and additionally I want them to be able to add data.
For that I use wtforms. I take the inputs, create a simple "SQL string" with it and execute it.
To make the input easier, I want to use dynamic SelectFields.
This is my approute:
#app.route('/table/fact_angestellte')
def fact_angestellte():
if current_user.is_authenticated:
mycursor.execute("SELECT * FROM dim_rolle WHERE ro_rolle !='Ansprechpartner' AND ro_rolle != 'Teilnehmer';")
choicesRolle = mycursor.fetchall()
form = InsertAngestelltenForm(choicesRolle)
print(form.choicesRolle)
mycursor.execute("SELECT * FROM fact_angestellte INNER JOIN dim_rolle ON fact_angestellte.an_rolle_fk = dim_rolle.ro_id_pk;")
data = mycursor.fetchall()
return render_template('tables/fact_angestellte.html', data=data, form=form)
else:
return redirect(url_for('login'))
The form gets created successfully and if I try to print form.choicesRolle (in the approute), it also gives me the correct output in the console. But when I go on my website, the SelectField still has the default value of choicesRolle.
class InsertAngestelltenForm(FlaskForm):
choicesRolle =[]
nachname = StringField('Nachname', validators=[DataRequired()])
vorname = StringField('Vorname',validators=[DataRequired()])
geschlecht = SelectField('Geschlecht', choices=[('maennlich', 'männlich'), ('weiblich', 'weiblich')], validators=[DataRequired()])
postleitzahl = StringField('Postleitzahl | FK', validators=[DataRequired()])
strasse = StringField('Straße und Nummer', validators=[DataRequired()])
rolle = SelectField('Rolle', choices=choicesRolle, validators=[DataRequired()])
submit = SubmitField('Eintrag hinzufügen')
def __init__(self, choicesRolle):
super().__init__()
self.choicesRolle = choicesRolle
print(self.choicesRolle)
So my problem is: the object has the correct attributes, but somehow they don't "reach" the template.
Any help is appreciated.
Greetings
Per the WTForms documentation, the choices keyword is only evaluated once. In your example, this means it's evaluating to the empty array you set in the choicesRolle class attribute, and that's what's being passed to Flask. You need to set the choices after the form is instantiated, not during it.
On your form class, remove the entire __init__ method, the choicesRolle class attribute, and the choices parameter from the rolle SelectField. Then, in your fact_angestellte view function, set the form's choices after you instantiate it, as follows:
choicesRolle = mycursor.fetchall()
form = InsertAngestelltenForm()
form.rolle.choices = choicesRolle
This should work...let me know. Note that I'm not sure what data is being returned from mycursor.fetchall() as you don't really describe, but the SelectField choices needs to be a list of values...WTForms by default coerces each value to unicode.

Multiple Form with Single Submit Button

I'm currently working with django project. I had to filter the data store on the database based on the user input on form (at template) as looked below.
On form user either enter value or leave it blank. So what I have to do is first find the (valid) user input and then fire appropriate query to display data as user input in the form. So final result should be displayed on table at template.
As I'm new to django, how should I have to pass the data and fire query to represent data at multiple field. As help or link related to these type problem are expected. ( I just able to filter from the database with only one form and had no concept to solve this.)
Model of my temp project is as below.
class exReporter(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
email = models.EmailField()
gender = models.CharField(max_length=1)
age = models.IntegerField()
label = models.IntegerField()
There are a number of approaches you can take, but here is one solution you can use that involves chaining together the appropriate filters based on the form's posted data:
*Note: To conform to Python's naming convention, rename exReporter class to ExReporter.
# views.py
def process_ex_reporter_form(request):
if request.method == "POST":
# ExReporterForm implementation details not included.
ex_reporter_form = ExReporterForm(request.POST)
if ex_reporter_form.is_valid():
# If form field has no data, cleaned data should be None.
gender = ex_reporter_form.cleaned_data['gender']
age_start = ex_reporter_form.cleaned_data['age_start']
age_end = ex_reporter_form.cleaned_data['age_end']
aggregation_group = ex_reporter_form.cleaned_data['aggregation_group']
aggregation_id = ex_reporter_form.cleaned_data['aggregation_id']
ex_reporters = ExReporter.objects.get_ex_reporters(gender, age_start,
age_end, aggregation_group, aggregation_id)
else:
# Pass back form for correction.
pass
else:
# Pass new form to user.
pass
# models.py
class ExReporterManager(models.Manager):
def get_ex_reporters(self, gender, age_start, age_end, aggregation_group,
aggregation_id):
ex_reporters = super(ExReporterManager, self).get_query_set().all()
# Even though the filters are being applied in separate statements,
# database will only be hit once.
if ex_reporters:
if gender:
ex_reporters = ex_reporters.filter(gender=gender)
if age_start:
ex_reporters = ex_reporters.filter(age__gt=age_start)
if age_end:
ex_reporters = ex_reporters.filter(age__lt=age_end)
# Apply further filter logic for aggregation types supported.
return ex_reporters

Django - preventing duplicate records

I have a list of client records in my database. Every year, we generate a single work order for each client. Then, for each work order record, the user should be able to create a note that is specific to the work order. However, not all work orders need a note, just some.
Now, I can't simply add a note field to the work order because some times, we need to create the note before the work order is even generated. Sometimes this note is specific to a work order that won't happen for 2-3 years. Thus, the notes and the work order must be independent, although they will "find" each other when they both exist.
OK, so here's the situation. I want the user to be able to fill out a very simple note form, where they have two fields: noteYear and note. Thus, all they do is pick a year, and then write the note. The kicker is that the user should not be able to create two notes for the same year for the same client.
What I'm trying to get as is validating the note by ensuring that there isn't already a note for that year for that client. I'm assuming this would be achieved by a custom is_valid method within the form, but I can't figure out how to go about doing that.
This is what I tried so far (note that I know it's wrong, it doesn't work, but it's my attempt so far):
Note that systemID is my client record
My model:
class su_note(models.Model):
YEAR_CHOICES = (
('2013', 2013),
('2014', 2014),
('2015', 2015),
('2016', 2016),
('2017', 2017),
('2018', 2018),
('2019', 2019),
('2020', 2020),
('2021', 2021),
('2022', 2022),
('2023', 2023),
)
noteYear = models.CharField(choices = YEAR_CHOICES, max_length = 4, verbose_name = 'Relevant Year')
systemID = models.ForeignKey(System, verbose_name = 'System ID')
note = models.TextField(verbose_name = "Note")
def __unicode__(self):
return u'%s | %s | %s' % (self.systemID.systemID, self.noteYear, self.noteType)
And my form:
class SU_Note_Form(ModelForm):
class Meta:
model = su_note
fields = ('noteYear', 'noteType', 'note')
def is_valid(self):
valid = super (SU_Note_Form, self).is_valid()
#If it is not valid, we're done -- send it back to the user to correct errors
if not valid:
return valid
# now to check that there is only one record of SU for the system
sysID = self.cleaned_data['systemID']
sysID = sysID.systemID
snotes = su_note.objects.filter(noteYear = self.cleaned_data['noteYear'])
for s in snotes:
if s.systemID == self.systemID:
self._errors['Validation_Error'] = 'There is already a startup note for this year'
return False
return True
EDIT -- Here's my solution (thanks to janos for sending me in the right direction)
My final form looks like this:
class SU_Note_Form(ModelForm):
class Meta:
model = su_note
fields = ('systemID', 'noteYear', 'noteType', 'note')
def clean(self):
cleaned_data = super(SU_Note_Form, self).clean()
sysID = cleaned_data['systemID']
sysID = sysID.systemID
try:
s = su_note.objects.get(noteYear = cleaned_data['noteYear'], systemID__systemID = sysID)
print(s)
self.errors['noteYear'] = "There is already a note for this year."
except:
pass
return cleaned_data
For anyone else looking at this code, the only confusing part is the line that has: sysID = sysID.systemID. The systemID is actually a field of another model - even though systemID is also a field of this model -- poor design, probably, but it works.
See this page in the Django docs:
https://docs.djangoproject.com/en/dev/ref/forms/validation/
Since your validation logic depends on two fields (the year and the systemID), you need to implement this using a custom cleaning method on the form, for example:
def clean(self):
cleaned_data = super(SU_Note_Form, self).clean()
sysID = cleaned_data['systemID']
sysID = sysID.systemID
try:
su_note.objects.get(noteYear=cleaned_data['noteYear'], systemID=systemID)
raise forms.ValidationError('There is already a startup note for this year')
except su_note.DoesNotExist:
pass
# Always return the full collection of cleaned data.
return cleaned_data

Django form cache or reload issue

I have a page to insert value to db.
After inserting am loading another page with a drop down listing the db values
But the inserted value is not listing in dropdown
The problem is not with transaction/commit etc. The query to retrieve the data for the drop down in second form is correct.
Form1(first page)
class Organization(forms.Form):
orgList = getOrgUnitList()
orgUnit = forms.CharField(label=u'Organization Name',
max_length=50,
error_messages={'required':'Organization name is required field.'})
parentOrg= forms.ChoiceField(label=u'Parent Organization',
choices=[(u'Select',u'Select')]+orgList,
error_messages={'required':'Organization name is required field.'})
Form2(Second page)
class User(forms.Form):
orgUnitList = getOrgUnitList()
email = forms.EmailField(label=u'Email',
max_length=50,
error_messages={'required':'Email is required field'})
orgUnit = forms.ChoiceField(label=u'Organizational Unit',
choices=orgUnitList,
error_messages={'required':'Organizational unit is required field'})
Query
def getOrgUnitList():
orgUnitList = list(OrganizationUnit.objects.values_list
('OrgUnitID','OrgUnitName').order_by('OrgUnitName'))
return orgUnitList
but when i tried to bind the choices in view it is working
working code
*view*
def user()
template = get_template('AddUser.html')
form = AddUser()
orgUnitList = getOrgUnitList()
del objAdminUIDA
form.fields['orgUnit'].widget.choices=orgUnitList
variables = RequestContext(request,{'form':form})
output = template.render(variables)
del form
return HttpResponse(output)
But i cant give the dropdown choice in view i want to give choices in form.i need a solution for form2
Firstly, the orgList is evaluated in form definition, that's why choices don't change. You should place getOrgUnitList in form's __init__ (or in some other method).
Secondly, you don't pass any data to the form, may be you want
form = AddUser(request.POST or None)

Django - processing objects after .filter(...) or .all(...)

Still learning Django, so not sure if there's a nice way to do this.
I have a few models with specific attributes (all use Item as base class), and a metadata table (id, language, type, value) used to store any extra attributes that could be potentially associated with instances of any of those models (code below). These models are used with a form / template, simple web-based CRUD.
Right now, I call .save_metadata(...) and .load_metadata(...) explicitly, and use .form_initia(...) to populate the form with metadata that isn't explicitly in the model.
I'm looking for a way to handle this automatically -- basically implementing a model with a variable number of fields, key ones are columns in the model's table, the other ones are rows in the metadata table, and are instance-specific. Is there a way of hooking a method after objects.get(...) or objects.filter(...) etc? I've messed with custom managers and looked into signals, but nothing seems to lead towards an acceptable solution.
class Item(models.Model):
mdata = ['title'] # metadata associated with item
user = models.ForeignKey(User)
created = models.DateTimeField(auto_now_add = True)
status = models.IntegerField(default=0, choices = ([(0,'Staged'), (1,'Published'),(2,'Archived'), ]))
def set_status(self, s):
self.status = s
self.save()
# stores metadata attributes associated with current item
def save_metadata(self, lang, form):
for mt in self.mdata:
try:
md = Metadata.objects.get(item=self, lang=lang, t=mt)
except Metadata.DoesNotExist:
md = Metadata.objects.create(item=self, lang=lang, t=mt)
md.v=form.cleaned_data[mt]
md.save()
# retrieves metadata attributes associated with current item
def load_metadata(self, lang):
for mt in self.mdata:
self.__dict__[mt] = None
try:
self.__dict__[mt] = Metadata.objects.get(item=self, t=mt, lang=lang).v
except Metadata.DoesNotExist:
md = Metadata.objects.filter(item=self, t=mt)
if len(md) > 0:
self.__dict__[mt] = md[0].v
# provides metadata attributes associated with current item needed to populate a form
def form_initial(self, seed=None):
meta = {}
for mt in self.mdata:
meta[mt] = self.__dict__[mt]
#meta[mt] = 'test'
if seed:
meta = dict(meta.items() + seed.items())
return meta
# used to store various metadata associated with models derived from Item
class Metadata(models.Model):
item = models.ForeignKey(Item)
lang = models.CharField(max_length = 8)
t = models.CharField(max_length = 250)
v = models.CharField(max_length = 2500)