How to uniquely identify a model instance in django - 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.

Related

copy values of multiple ModelMutipleChoiceField into one after Post using MPTT

I've been struggling with this issue all day and hope someone can help.
I have all my hierarchies classified by category in the same table.
during the form creation, I want to separate each hierarchy by category and render it using a ModelMutipleChoiceField his way not all hierarchies are displayed together.
The problem comes when the form is submitted, as I need to go through each ModelMutipleChoiceField field and get the selected values and copy these to the model field before saving the form. however, I am not able to iterate through the ModelMutipleChoiceField and get the selected values. I also don't know how to set these values on the ModelField
NOTE: The number of hierarchies can vary.
here is my code:
I'm using Django MPTT and create my hierarchy structure using 2 models.
one is the category(Hierarchy) and the other is the nodes of the hierarchy (HierarchyNode_MPTT).
Then I created a separate model that has ManyToManyField pointing to the HierarchyNode_MPTT.
Models.py
class Hierarchy(models.Model):
ID = kp.ObjectIDField()
name = kp.ObjectNameField()
ext_hierarchy = kp.ObjectTechnicalID()
seq_no = kp.SeqNoField(unique=True)
mptt_seq_no = models.PositiveIntegerField()
class HierarchyNode_MPTT(MPTTModel):
id = kp.ObjectIDField()
name = kp.ObjectNameField()
description = kp.ObjectDescriptionField()
ext_node_id = kp.ObjectShortNameField()
parent = TreeForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='children')
hierarchy = models.ForeignKey(Hierarchy, on_delete=models.CASCADE, null=True, blank=True, related_name='children')
class Configuration(models.Model):
uuid = kp.ObjectIDField()
name = kp.ObjectNameField()
description = kp.ObjectDescriptionField()
hierarchy_nodes = models.ManyToManyField(HierarchyNode_MPTT)
Then I created the form and implement the init method to automatically create as many hierarchies as I need.
form.py
class ConfigurationCreateForm(forms.ModelForm):
class Meta:
model = ForecastConfiguration
exclude = ['uuid', 'hierarchy_nodes']
def __init__(self, user, *args, **kwargs):
super().__init__(*args, **kwargs)
hierarchies = Hierarchy.objects.all()
for hierarchy in hierarchies:
field_name = 'hierarchy_%s' % (hierarchy.mptt_seq_no,)
self.fields[field_name] = TreeNodeMultipleChoiceField(queryset=HierarchyNode_MPTT.objects.all().filter(hierarchy=hierarchy),label=hierarchy.name, required=True)
try:
self.initial[field_name] = HierarchyNode_MPTT.objects.root_node(tree_id=hierarchy.mptt_seq_no)
except IndexError:
self.initial[field_name] = ''
def copy_hierarchies(self, *args, **kwargs):
hierarchies = Hierarchy.objects.all()
choice_list = list()
for hierarchy in hierarchies:
field_name = 'hierarchy_%s' % (hierarchy.mptt_seq_no,)
selected_values = self.cleaned_data.get(field_name)
for selection in selected_values:
choice_list.append(selection)
self.initial['hierarchy_nodes'] = choice_list
Finally, the idea was to implement the post method on the View to loop over the created hierarchies and then assign the value to the model field called 'hierarchy_nodes'
view.py
class ConfigurationCreateView(CreateView):
model = Configuration
form_class = ConfigurationCreateForm
template_name = 'frontend/base/config_create.html'
def get(self, request, *args, **kwargs):
form = ConfigurationCreateForm(user=request.user)
return render(request, self.template_name, {'form': form})
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
form.copy_hierarchies(*args, **kwargs)
if form.is_valid():
fcc_form = form.save(commit=True)
messages.add_message(self.request, messages.INFO, 'Your Forecast Configurations has been saved')
return redirect(reverse('planning_detail', kwargs={'uuid': self.fcc_form.uuid}))
else:
messages.add_message(self.request, messages.ERROR, 'Error when creating the Forecast Configuration')
return render(request, self.template_name, {'form': form})
As you can see I created a method in my form called copy_hierarchies which is where I was planning to copy the hierarchy values, this is the method where I'm having problems.
if there is an easier way to perform this using Javascript, I'm open to these options.
Thanks in advance.
I wasn't able to solve this using multi-choice field, however, the following is the solution for a ChoiceField (single selection)
1) Changed my view.py post method to save the object.
2) After the model is saved I loop over the request input filed and append the values to the created instance.
3) Save the instance.
4) delete my copy_hierarchies method in forms.py
here is the code snippet created in views.py
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
if form.is_valid():
fcc = form.save()
for key in self.request.POST:
# check only the ones w/ 'hierarchy_#'
if key.startswith('hierarchy_'):
# get form field object
id = self.request.POST[key]
node = HierarchyNode_MPTT.objects.get(id=id)
# add to object instance
fcc.hierarchy_nodes.add(node)
fcc.save()

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.

Save a django object getting another model instance as foreign key from a form

I'm stuck trying to save an instance of a model that gets from a form an instance of another model as foreign key.
Models
class Customer(models.Model):
owner = models.ForeignKey(User)
custname = models.CharField()
class Appointment(models.Model):
user = models.ForeignKey(User)
start = models.DateTimeField()
end = models.DateTimeField()
customer = models.ForeignKey(Customer)
Form
class AppointmentForm(forms.Form):
basedate = forms.DateField()
start = forms.TimeField(widget=forms.Select())
end = forms.IntegerField(widget=forms.Select())
customer = forms.ModelMultipleChoiceField(queryset=Customer.objects.all())
The method that I'm not able to get working in a generic FormView:
def form_valid(self, form):
if form.is_valid():
appointment = Appointment()
appointment.user = self.request.user
basedate = form.cleaned_data['basedate']
start = form.cleaned_data['start']
duration = form.cleaned_data['end']
appointment.start = datetime.datetime.combine(basedate, start)
appointment.end = appointment.start + datetime.timedelta(minutes=duration)
appointment.save()
return super(AppointmentCreate, self).form_valid(form)
What should I add in the last method to read the foreign key customer from the form, and therefore pass it to the appointment? And is there any way of filtering so that in the form only appear customers belonging to the request.user?
Many thanks in advance for your help.
Something like this should work. A couple of things:
1) I changed the form field to a ModelChoiceField instead of multiple choice. You'll want to use a ModelChoiceField to show the relationship. I changed this from MultipleChoice since, according to your model, you only want to save one choice. You can read more on ModelChoiceFields here: https://docs.djangoproject.com/en/dev/ref/forms/fields/
2) In your forms, I changed the choice query to customer = forms.ModelChoiceField(queryset=Customer.objects.filter(owner=request.user). This will filter for Customers of the specific user only.
forms.py
class AppointmentForm(forms.Form):
def __init__(self, *args, **kwargs):
self.request = kwargs.pop("request")
super(AppointmentForm, self).__init__(*args, **kwargs)
basedate = forms.DateField()
start = forms.TimeField(widget=forms.Select())
end = forms.IntegerField(widget=forms.Select())
customer = forms.ModelChoiceField(queryset=Customer.objects.filter(owner=request.user))
views.py
def form_valid(self, form):
if request.method=='POST':
form = AppointmentForm(request.POST, request=request)
if form.is_valid():
appointment = Appointment()
appointment.user = self.request.user
basedate = form.cleaned_data['basedate']
start = form.cleaned_data['start']
duration = form.cleaned_data['end']
appointment.customer = form.cleaned_data['customer']
appointment.start = datetime.datetime.combine(basedate, start)
appointment.end = appointment.start + datetime.timedelta(minutes=duration)
appointment.save()
return super(AppointmentCreate, self).form_valid(form)
else:
form = AppointmentForm()
Finally I did it. The key was to override the get method of FormView class in views.py, rather than modifying the init in forms.py:
forms.py:
class AppointmentForm(forms.Form):
basedate = forms.DateField()
start = forms.TimeField(widget=forms.Select())
end = forms.IntegerField(widget=forms.Select())
customer = forms.ModelChoiceField(queryset=Customer.objects.all())
...
views.py:
def get(self, request, *args, **kwargs):
"""
Handles GET requests and instantiates a blank version of the form.
"""
choices_start, choices_duration = self._get_choices()
form_class = self.get_form_class()
form = self.get_form(form_class)
form.fields['start'].widget=forms.Select(choices=choices_start)
form.fields['end'].widget=forms.Select(choices=choices_duration)
form.fields['customer'].queryset=Customer.objects.filter(owner=request.user)
return self.render_to_response(self.get_context_data(form=form))
#Dan: Many thanks for your effort in helping me out.

Validating Unique Fields in Django

I don't know if I'm approaching the problem in the right way. The intended outcome is to have a form that displays only name and description. Once the user submits the form I want to add the current user as owner and check if there's already an entry that has the same name and user. If there is, I want to return the form with errors. If not, I want to save Status.
My model:
class Status(models.Model):
name = models.CharField(max_length=100)
description = models.TextField(blank=True)
owner = models.ForeignKey(User)
active = models.BooleanField(default=True)
class Meta:
unique_together = ('name','owner')
My View:
def settings_status(request):
status_form = StatusForm()
if request.method == 'POST':
status_form = StatusForm(request.POST)
if status_form.is_valid():
new_status = Status()
new_status.name = status_form.cleaned_data['name']
new_status.description = status_form.cleaned_data['description']
new_status.owner = request.user
new_status.save()
return render_to_response('base/settings_status.html',{
'status_form' : status_form,
}, context_instance=RequestContext(request))
I have tried numerous things, but I keep running into the problem that if I add owner to the object separately then it isn't available to the model's clean function and therefore can't be used to check if name and owner are unique.
Several ways to do this:
for example, passing in the user (owner) to the form:
forms.py:
class StatusForm(forms.Form):
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user','')
super(StatusForm, self).__init__(*args, **kwargs)
self.fields['name'] = forms.CharField(label='Name')
self.fields['description'] = CharField(label='Description', widget=forms.Textarea)
def clean(self):
cleaned_data = self.cleaned_data
name = cleaned_data.get('name')
if Status.objects.filter(name=name, owner=self.user).exists():
self._errors['name'] self.error_class(['Status with this name exists'])
return cleaned_data
views.py:
def settings_status(request):
if request.method == 'POST':
status_form = StatusForm(request.POST, user=request.user)
if status_form.is_valid():
new_status = Status()
new_status.name = status_form.cleaned_data['name']
new_status.description = status_form.cleaned_data['description']
new_status.owner = request.user
new_status.save()
else:
status_form = StatusForm(user=request.user)
context = {'status_form':status_form,}
return render_to_response('base/settings_status.html', context,
context_instance=RequestContext(request))
Also look at setting initial data depending on your form setup and consider using a ModelForm.

Adding extra fields not working in my django form

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