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
.