Adding extra fields not working in my django form - django

I have a model with a custom property
class RecipeIngredient(models.Model):
recipe = models.ForeignKey(Recipe)
ingredient = models.ForeignKey(Ingredient)
serving_size = models.ForeignKey(ServingSize)
quantity = models.IntegerField()
order = models.IntegerField()
created = models.DateTimeField(auto_now_add = True)
updated = models.DateTimeField(auto_now = True)
def _get_json_data(self):
return u'%s %s' % (self.id, self.ingredient.name)
json_data = property(_get_json_data)
I am trying to display the property 'json_data' in my template.
I have this piece of code in my form
class RecipeIngredientForm(forms.ModelForm):
json_data = forms.CharField(widget=forms.HiddenInput())
def __init__(self, *args, **kwargs):
super(RecipeIngredientForm, self).__init__(*args, **kwargs)
print('here')
if kwargs.has_key('instance'):
instance = kwargs['instance']
print(instance)
self.initial['json_data'] = instance.json_data
I know the data in my property 'json_data' is not valid, but I am unable to see the data from this property in my template.
In my views, I have this piece of code
RecipeIngredientFormSet = inlineformset_factory(models.Recipe, models.RecipeIngredient, form=forms.RecipeIngredientForm, extra=0)
recipe_id = int(id)
objRecipe = models.Recipe.objects.get(id=recipe_id)
recipe = forms.RecipeForm(instance=objRecipe)
recipeIngredients = RecipeIngredientFormSet(instance = objRecipe)
I guess my question is how do I display data from an extra model field?

pass the data of the extra fields as initial data to the form
initial_data = [{'json_data': objRecipe.json_data}]
recipeIngredients = RecipeIngredientFormSet(initial=initial_data, instance = objRecipe)
for correct usage of initial data refer using initial data with formset

Related

How to uniquely identify a model instance in django

I have program that keeps book records for different schools I have a Student model that enables each school to upload the students from an excel. I however get an error because already there are two schools each having form 1 form 2 and form 3. It returns MultipleObjectsReturned error.
Setting the filed as unique also does not allow other schools to create the same klass.
How is it that I can be able to uniquely identify every instannce ok Klass model so it returns no error.
get() returned more than one Klass -- it returned 2!
class ImportStudentsResource(resources.ModelResource):
school = fields.Field(attribute = 'school',column_name='school', widget=ForeignKeyWidget(School, 'name'))
klass = fields.Field(attribute = 'klass',column_name='class', widget=ForeignKeyWidget(Klass, 'name'))
stream = fields.Field(attribute = 'stream',column_name='stream', widget=ForeignKeyWidget(Stream, 'name'))
class Meta:
model = Student
fields = ('school','student_id','name','year','klass','stream')
import_id_fields = ('student_id',)
import_order = ('school','student_id','name','year','klass','stream')
class uploadStudents(LoginRequiredMixin,View):
context = {}
def get(self,request):
form = UploadStudentsForm()
self.context['form'] = form
return render(request,'libman/upload_student.html',self.context)
def post(self, request):
form = UploadStudentsForm(request.POST , request.FILES)
data_set = Dataset()
if form.is_valid():
file = request.FILES['file']
extension = file.name.split(".")[-1].lower()
resource = ImportStudentsResource()
if extension == 'csv':
data = data_set.load(file.read().decode('utf-8'), format=extension)
else:
data = data_set.load(file.read(), format=extension)
result = resource.import_data(data_set, dry_run=True, collect_failed_rows=True, raise_errors=True)
if result.has_validation_errors() or result.has_errors():
messages.success(request,f'Errors experienced during import.')
print("error", result.invalid_rows)
self.context['result'] = result
return redirect('upload_students')
else:
result = resource.import_data(data_set, dry_run=False, raise_errors=False)
self.context['result'] = None
messages.success(request,f'Students uploaded successfully.')
else:
self.context['form'] = UploadStudentsForm()
return render(request, 'libman/upload_student.html', self.context)
class Student(models.Model):
school = models.ForeignKey(School, on_delete=models.CASCADE)
name = models.CharField(max_length=200)
now = datetime.datetime.now()
YEAR = [(str(a), str(a)) for a in range(now.year-2, now.year+2)]
year = models.CharField(max_length=4, choices = YEAR)
student_id = models.CharField(max_length=20,unique=True)
klass = models.ForeignKey(Klass,on_delete=models.CASCADE)
stream = models.ForeignKey(Stream,on_delete=models.CASCADE)
When you import your data, the logic cannot uniquely identify a unique instance of 'klass'. You are using the 'name' attribute of the foreign key relationship to class, however as you said, this 'name' field cannot uniquely identify an instance of the Klass, hence you get the MultipleObjectsReturned error.
The solution is to create a subclass of ForeignKeyWidget which uses an overridden get_queryset() which can uniquely identify the Klass. You will need to use an additional parameter which can be added to the query. I'm guessing that school_id might work (but I don't know your data model).
class KlassForeignKeyWidget(widgets.ForeignKeyWidget):
"""
Custom widget to lookup a Klass instance
"""
def __init__(
self,
school_id,
field="name",
*args,
**kwargs,
):
super().__init__(self.model, field=field, *args, **kwargs)
self.school_id = school_id
def get_queryset(self, value, row, *args, **kwargs):
return self.model.objects.filter(school_id=self.school_id)
Then you'll have to declare this custom widget in your Resource:
class ImportStudentsResource(resources.ModelResource):
def __init__(self, school_id):
super().__init__()
self.school_id = school_id
self.fields["klass"] = fields.Field(
attribute="klass",
column_name="class",
widget=myapp.widgets.KlassForeignKeyWidget(
school_id,
),
)
Then when you instantiate the ImportStudentsResource you pass in the school_id.

filtering relational models inside django forms

i have a model which has a foreign key relation with two oder models one of them is 'level'.
the view knows in which level you are based on a session variable,
and then filter the lessons
this is the lesson model:
class Lesson(models.Model):
level = models.ForeignKey(Level,on_delete=models.CASCADE)
subject = models.ForeignKey(Subject,on_delete=models.CASCADE)
chapiter = models.CharField(max_length=200)
lesson = models.CharField(max_length=200)
skill = models.CharField(max_length=200)
vacations = models.IntegerField()
link = models.URLField(max_length=700,null=True,blank=True)
remarques = models.TextField(null=True,blank=True)
order = models.IntegerField()
created = models.DateTimeField(auto_now_add=True, auto_now=False)
updated = models.DateTimeField(auto_now=True)
state = models.BooleanField(default=False)
now this is my cbv to create a new lesson:
class GlobalLessonView(CreateView):
model = Lesson
form_class = GlobalLessonForm
success_url = reverse_lazy('globalform')
and this is the form:
class GlobalLessonForm(forms.ModelForm):
class Meta:
model = Lesson
fields = '__all__'
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['subject'].queryset = Subject.objects.none() #change to .all() to see list of all subjects
if 'level' in self.data:
try:
level_id = int(self.data.get('level'))
self.fields['subject'].queryset = Subject.objects.extra(where=[db_name+'.scolarité_subject.id in( select subject_id from '+db_name+'.scolarité_levelsubject where level_id='+level_id+')'])
except (ValueError, TypeError):
pass # invalid input from the client; ignore and fallback to empty City queryset
elif self.instance.pk:
self.fields['subject'].queryset = self.instance.level.subject_set
one of the main conditions is to filter level by a session variable
but the form does not accept request.session
so is there any way to change the levels that shows up at the form from the class based view,or there any way to pass request.session to form.py
Add this to GlobalLessonView:
def get_form_kwargs(self):
"""Pass request to form."""
kwargs = super().get_form_kwargs()
kwargs.update(request=self.request)
return kwargs
Then change the constructor definition in GlobalLessonForm to:
def __init__(self, request, *args, **kwargs):
Then you will be able to reference request.session in GlobalLessonForm.

Instantiate a modelForms MultiSelectCheckBox widget

I am using ModelForms with a ModelMultipleChoiceField widget. I have 2 questions:
My checkbox widget for Treatment Options from SelectOptionForStateForm renders existing selections from the stateoption table in my database. How does it know to look in that table for existing records? In my views.py I am only passing the disease and state objects which do not look at the stateoption table.
How do I instantiate my SelectOutcomeForOptionForm so that my Treatment Outcomes checkbox is also pre-selected from the stateoptionoutcome table in my database?
forms.py
class SelectOptionForStateForm(forms.ModelForm):
class Meta:
model = State
exclude = ['state', 'relevantdisease']
def __init__(self, *args, **kwargs):
disease=kwargs.pop('disease', None)
super(SelectOptionForStateForm, self).__init__(*args, **kwargs)
self.fields['relevantoption']=forms.ModelMultipleChoiceField(queryset=Option.objects.filter(relevantdisease_id=disease),required=True, widget=forms.CheckboxSelectMultiple)
self.fields['relevantoption'].label="Treatment Options"
class SelectOutcomeForOptionForm(forms.ModelForm):
class Meta:
model = StateOption
exclude = ['partstate', 'partoption']
def __init__(self, *args, **kwargs):
disease=kwargs.pop('disease', None)
super(SelectOutcomeForOptionForm, self).__init__(*args, **kwargs)
self.fields['relevantoutcome']=forms.ModelMultipleChoiceField(queryset=Outcome.objects.filter(relevantdisease_id=disease),required=True, widget=forms.CheckboxSelectMultiple)
self.fields['relevantoutcome'].label="Treatment Outcomes"
views.py
def stateoptionoutcome(request, disease_id, state_id):
state = get_object_or_404(State, pk=state_id)
disease = get_object_or_404(Disease, pk=disease_id)
if request.method == "POST":
optionForm = SelectOptionForStateForm(request.POST, disease=disease, instance=state)
outcomeForm = SelectOutcomeForOptionForm(request.POST, disease=disease, instance=state)
if optionForm.is_valid() and outcomeForm.is_valid():
#Deletes state objects so there are no duplicate options in the database
try:
state_option = StateOption.objects.filter(partstate=state).delete()
except StateOption.DoesNotExist:
state_option = None
#Saves user options to database
for option_id in request.POST.getlist('relevantoption'):
state_option = StateOption.objects.create(partstate=state, partoption_id=int(option_id))
#Deletes stateoption objects found in StateOptionOutcome
try:
state_option_outcome = StateOptionOutcome.objects.filter(stateoption=state_option).delete()
except StateOptionOutcome.DoesNotExist:
state_option_outcome = None
#Saves user outcomes to database
for outcome_id in request.POST.getlist('relevantoutcome'):
state_option_outcome = StateOptionOutcome.objects.create(stateoption=state_option, relevantoutcome_id=int(outcome_id))
return HttpResponseRedirect(reverse('diseasestateoptionlist', kwargs={'disease_id':disease_id, 'state_id':state_id}))
models.py
class State(models.Model):
state = models.CharField(max_length=255)
relevantdisease = models.ForeignKey(Disease)
relevantoption = models.ManyToManyField(Option, through='StateOption')
class StateOption(models.Model):
partstate = models.ForeignKey(State)
partoption = models.ForeignKey(Option)
relevantoutcome = models.ManyToManyField(Outcome, through='StateOptionOutcome')
class StateOptionOutcome(models.Model):
stateoption = models.ForeignKey(StateOption)
relevantoutcome = models.ForeignKey(Outcome)
outcomevalue = models.CharField(max_length=20)
I was able to solve #2 using this:
stateoptionlist = StateOption.objects.filter(partstate_id=state_id)
if not stateoptionlist:
stateoption = state
else:
stateoption = stateoptionlist[0]
I pass the state instance if the stateoptionlist returns an empty list (ie, there are no records in the StateOption table). Updating the views.py, for every SelectOutcomeForOptionForm, my instance is replaced with stateoption.
I'm interested to see if there's a more efficient way of solving #2.

Form not saving to database "through" intermediate tables

I have a custom form that is not saving to the database. I do not get any errors but the values do not save to the database. Any ideas?
views.py
def diseasestateoption(request, disease_id, state_id):
state = get_object_or_404(State, pk=state_id)
disease = get_object_or_404(Disease, pk=disease_id)
if request.method == "POST":
form = UpdateStateWithOptionsForm(request.POST, instance=state)
if form.is_valid():
for option_id in request.POST.getlist('options'):
state_option = StateOption.objects.create(partstate=state, partoption_id=int(option_id))
state_option.save()
return HttpResponseRedirect(reverse('success'))
else:
form = UpdateStateWithOptionsForm(instance=state)
models.py
class Option(models.Model):
relevantdisease = models.ForeignKey(Disease)
option = models.CharField(max_length=300)
class State(models.Model):
state = models.CharField(max_length=300, verbose_name='state')
relevantdisease = models.ForeignKey(Disease, verbose_name="disease")
relevantoption = models.ManyToManyField(Option, through='StateOption')
class StateOption(models.Model):
parttstate = models.ForeignKey(State)
partoption = models.ForeignKey(Option)
forms.py
class UpdateStateWithOptionsForm(forms.ModelForm):
class Meta:
model = State
exclude = ['state', 'relevantdisease']
def __init__(self, *args, **kwargs):
super(UpdateStateWithOptionsForm, self).__init__(*args, **kwargs)
self.fields['relevantoption']=forms.ModelMultipleChoiceField(queryset=Option.objects.all(),required=True, widget=forms.CheckboxSelectMultiple)
I think Problem is with getting option from POST, use-
request.POST.getlist('relevantoption')
in stead of
request.POST.getlist('options')
apart, why to use form here for single multiple choice field, even where you are modifying choices also and not using form.save too.

Django how to save a custom formset

I've written the following custom formset, but for the life of me I don't know how to save the form. I've searched the Django docs and done extensive searches, but no one solution works. Lots of rabbit holes, but no meat ;-) Can someone point me in the right direction?
// views.py partial //
#login_required
def add_stats(request, group_slug, team_id, game_id, template_name = 'games/stats_add_form.html'):
if request.POST:
formset = AddStatsFormSet(group_slug=group_slug, team_id=team_id, game_id=game_id, data=request.POST)
if formset.is_valid():
formset.save()
return HttpResponseRedirect(reverse('games_game_list'))
else:
formset = TeamStatFormSet(group_slug=group_slug, team_id=team_id, game_id=game_id)
return render_to_response(template_name, {'formset': formset,})
// modles.py partial //
class PlayerStat(models.Model):
game = models.ForeignKey(Game, verbose_name=_(u'sport event'),)
player = models.ForeignKey(Player, verbose_name=_(u'player'),)
stat = models.ForeignKey(Stat, verbose_name=_(u'statistic'),)
total = models.CharField(_(u'total'), max_length=25, blank=True, null=True)
class Meta:
verbose_name = _('player stat')
verbose_name_plural = _('player stats')
db_table = 'dfusion_playerstats'
def __unicode__(self):
return u'%s' % self.player
// forms.py
class TeamStatForm(forms.Form):
total = forms.IntegerField()
class BaseTeamStatsFormSet(BaseFormSet):
def __init__(self, *args, **kwargs):
self.group_slug = kwargs['group_slug']
self.team_id = kwargs['team_id']
self.game_id = kwargs['game_id']
self.extra = len(Stat.objects.filter(group__slug=self.group_slug))
del kwargs['group_slug']
del kwargs['game_id']
del kwargs['team_id']
super(BaseTeamStatsFormSet, self).__init__(*args, **kwargs)
def add_fields(self, form, index):
super(BaseTeamStatsFormSet, self).add_fields(form, index)
form.fields["stat"] = forms.ModelChoiceField(queryset = Stat.objects.filter(group__slug=self.group_slug))
form.fields["game"] = forms.ModelChoiceField(queryset = Game.objects.all())
form.fields["team"] = forms.ModelChoiceField(queryset = Team.objects.all())
form.fields["game"].initial = self.game_id
form.fields["team"].initial = self.team_id
TeamStatFormSet = formset_factory(TeamStatForm, BaseTeamStatsFormSet)
In your custom forms, you'll need to add a save() method that stuffs the form data into your models as needed. All of the data entered in the form will be available in a hash called cleaned_data[].
For example:
def save(self):
teamStat = TeamStat(game_id=self.cleaned_data['game_id'],team_id=self.cleaned_data['team_id'])
teamStat.save()
return teamStat
Only model forms and formsets come with a save() method. Regular forms aren't attached to models, so you have to store the data yourself. How to save a formset? from the Django mailing list has an example of saving data from a regular formset.
Edit: You can always add a save() method to a regular form or formset as gbc suggests. They just don't have one built-in.
I don't see a TeamStat model in your code snippets, but if you had one, your forms.py should look something like this:
class TeamStatForm(forms.ModelForm):
total = forms.IntegerField()
class Meta:
model = TeamStat
class BaseTeamStatsFormSet(BaseModelFormSet):
def __init__(self, *args, **kwargs):
self.group_slug = kwargs['group_slug']
self.team_id = kwargs['team_id']
self.game_id = kwargs['game_id']
self.extra = len(Stat.objects.filter(group__slug=self.group_slug))
del kwargs['group_slug']
del kwargs['game_id']
del kwargs['team_id']
super(BaseTeamStatsFormSet, self).__init__(*args, **kwargs)
def add_fields(self, form, index):
super(BaseTeamStatsFormSet, self).add_fields(form, index)
form.fields["stat"] = forms.ModelChoiceField(queryset = Stat.objects.filter(group__slug=self.group_slug))
form.fields["game"] = forms.ModelChoiceField(queryset = Game.objects.all())
form.fields["team"] = forms.ModelChoiceField(queryset = Team.objects.all())
form.fields["game"].initial = self.game_id
form.fields["team"].initial = self.team_id
TeamStatFormSet = modelformset_factory(TeamStatForm, BaseTeamStatsFormSet)
See Creating forms from models from the Django docs