Field 'id' expected a number but got <QuerySet [<Department: GEE>]> - python

I can't find the mistake here. I saw some similar questions but still can't fix it.
here is my models.py
class Department(models.Model):
name = models.CharField(max_length=100)
class CustomUser(AbstractUser):
department = models.ManyToManyField(Department, blank=True)
class Student(models.Model):
user = models.OneToOneField(CustomUser, on_delete=models.CASCADE, primary_key=True)
department = models.ManyToManyField(Department, blank=True)
forms.py
class StudentRegisterForm(UserCreationForm):
class Meta(UserCreationForm):
model = CustomUser
fields = ['department', ..]
widgets = {'department': forms.CheckboxSelectMultiple()}
def __init__(self, *args, **kwargs):
super(StudentRegisterForm, self).__init__(*args, **kwargs)
self.fields['department'].required = True
def clean_department(self):
value = self.cleaned_data.get('department')
if len(value) > 1:
raise forms.ValidationError("You can't select more than one department!")
return value
def save(self, commit=True):
user = super().save(commit=False)
user.save()
student = Student.objects.create(user=user)
student.department.add(self.cleaned_data.get('department')) <-- got error on this line
return (user, student)
When the CustomUser object is created, the Student object will also be created within save method. But for some reasons it gave me an error
TypeError: Field 'id' expected a number but got <QuerySet [<Department: GEE>]>.
Noticed that the Department objects were created within admin panel and also the department field within the Student model works just fine if I create it inside admin panel.

I fixed it by adding * into the add method like this
student.department.add(*self.cleaned_data.get('department'))

Related

Python 3 Django Rest Framework - how to add a custom manager to this M-1-M model structure?

I have these models:
Organisation
Student
Course
Enrollment
A Student belongs to an Organisation
A Student can enrol on 1 or more courses
So an Enrollment record basically consists of a given Course and a given Student
from django.db import models
from model_utils.models import TimeStampedModel
class Organisation(TimeStampedModel):
objects = models.Manager()
name = models.CharField(max_length=50)
def __str__(self):
return self.name
class Student(TimeStampedModel):
objects = models.Manager()
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
email = models.EmailField(unique=True)
organisation = models.ForeignKey(to=Organisation, on_delete=models.SET_NULL, default=None, null=True)
def __str__(self):
return self.email
class Course(TimeStampedModel):
objects = models.Manager()
language = models.CharField(max_length=30)
level = models.CharField(max_length=2)
def __str__(self):
return self.language + ' ' + self.level
class Meta:
unique_together = ("language", "level")
class EnrollmentManager(models.Manager):
def org_students_enrolled(self, organisation):
return self.filter(student__organisation__name=organisation).all()
class Enrollment(TimeStampedModel):
objects = EnrollmentManager()
course = models.ForeignKey(to=Course, on_delete=models.CASCADE, default=None, null=False, related_name='enrollments')
student = models.ForeignKey(to=Student, on_delete=models.CASCADE, default=None, null=False, related_name='enrollments')
enrolled = models.DateTimeField()
last_booking = models.DateTimeField()
credits_total = models.SmallIntegerField(default=10)
credits_balance = models.DecimalField(max_digits=5, decimal_places=2)
Notice the custom EnrollmentManager that allows me to find all students who are enrolled from a given organisation.
How can I add a custom Manager to retrieve all the courses from a given organisation whose students are enrolled?
What I have tried
I thought to create a CourseManager and somehow query/filter from that side of the relationship:
class CourseManager(models.Manager):
def org_courses_enrolled(self, organisation):
return self.filter(enrollment__student__organisation__name=organisation).all()
This works, but it gives me the same 100 enrollment records :(
What I am trying to get is:
based on a given organisation
find all students who are enrolled
and then (DISTINCT?) to get the list of enrolled courses for that org
This is the view:
class OrganisationCoursesView(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
serializer_class = CourseSerializer
queryset = Course.objects.get_courses(1)
and the url:
# The below should allow: /api/v1/organisations/1/courses/
router.register('api/v1/organisations/(?P<organisation_pk>\d+)/courses', OrganisationCoursesView, 'organisation courses')
UPDATE 1
Based on the answer from h1dd3n I tried this next:
class CourseManager(models.Manager):
def get_queryset(self):
return super(CourseManager, self).get_queryset()
def get_courses(self, organisation):
return self.get_queryset().filter(student__organisation_id=organisation)
but that throws an error (as I expected it would):
FieldError: Cannot resolve keyword 'student' into field. Choices are:
courses, created, id, language, level, modified, progress
UPDATE 2 - getting closer!
Ok with help from #AKX's comments:
class CourseManager(models.Manager):
def get_queryset(self):
return super(CourseManager, self).get_queryset()
def get_courses(self, organisation):
return self.get_queryset().filter(courses__student__organisation_id=organisation)
now DOES return courses, but it returns a copy for each enrolled student. So now I need to group them so each record only appears one time...
First you need to change self.filter to self.get_queryset().filter() or make a seperate method in the manager.
def get_queryset(self):
return super(CourseManager, self).get_queryset()
In manager create a function
def get_courses(self,organisation):
return self.get_queryset.filter(student__oraganisation=organisation)
This should return the students and you don't need to call .all() - the filtered qs either way returns you all the objects that it finds.
EDIT
Try this:
class CourseManager(models.Manager):
def get_queryset(self):
return super(CourseManager, self).get_queryset()
def get_courses(self, organisation):
return self.get_queryset().filter( \
enrollments__student__organisation_id=organisation).distinct()
UPDATE 2
You can try and play around with from django.db.models import Q https://docs.djangoproject.com/en/3.0/topics/db/queries/
or with annotate based on this answer Get distinct values of Queryset by field where you filter out each student so it would appear once.

Django Custom Admin Form bulk save implmentation

I am trying to implement a CSV Import in Django Admin and save bulk data corresponding to the CSV file's rows.
This is my Admin class:
class EmployeeAdmin(admin.ModelAdmin):
list_display = ('user', 'company', 'department', 'designation', 'is_hod', 'is_director')
search_fields = ['user__email', 'user__first_name', 'user__last_name']
form = EmployeeForm
This is my Form class:
class EmployeeForm(forms.ModelForm):
company = forms.ModelChoiceField(queryset=Companies.objects.all())
file_to_import = forms.FileField()
class Meta:
model = Employee
fields = ("company", "file_to_import")
def save(self, commit=True, *args, **kwargs):
try:
company = self.cleaned_data['company']
records = csv.reader(self.cleaned_data['file_to_import'])
for line in records:
# Get CSV Data.
# Create new employee.
employee = CreateEmployee(...)
except Exception as e:
raise forms.ValidationError('Something went wrong.')
My Employee class is:
class Employee(models.Model):
user = models.OneToOneField(User, primary_key=True)
company = models.ForeignKey(Companies)
department = models.ForeignKey(Departments)
mobile = models.CharField(max_length=16, default="0", blank=True)
gender = models.CharField(max_length=1, default="m", choices=GENDERS)
image = models.ImageField(upload_to=getImageUploadPath, null=True, blank=True)
designation = models.CharField(max_length=64)
is_hod = models.BooleanField(default=False)
is_director = models.BooleanField(default=False)
When I upload my file and click save, it shows me this error:
'NoneType' object has no attribute 'save'
with exception location at:
/usr/local/lib/python2.7/dist-packages/django/contrib/admin/options.py in save_model, line 1045
EDIT I understand I need to put a call to super.save, but I am unable to figure out where to put the call, because the doc says that the save method saves and returns the instance. But in my case, there is no single instance that the superclass can save and return. Wham am I missing here?
TIA.
You should just add the super().save() to the the end of the function:
def save(self, *args, commit=True, **kwargs):
try:
company = self.cleaned_data['company']
records = csv.reader(self.cleaned_data['file_to_import'])
for line in records:
# Get CSV Data.
# Create new employee.
employee = CreateEmployee(...)
super().save(*args, **kwargs)
except Exception as e:
raise forms.ValidationError('Something went wrong.')

Django Form - checking every object without showing them

I created two models:
class Country(models.Model):
name = models.CharField(verbose_name="Name of country", max_length=100, default="Australia")
number = models.IntegerField(verbose_name="number of country", default="1")
def __unicode__(self):
return self.name
class World(models.Model):
country = models.ManyToManyField(Country)
name = models.CharField(verbose_name="New Map", max_length=100)
def __unicode__(self):
return self.name
I want to give user possibility to create new world on site, but during creating he can't uncheck any object from a list (every object must be checked but he can't see them).
I created a form.py:
class WorldForm(forms.ModelForm):
country = forms.ModelMultipleChoiceField(queryset = Country.objects.all(), widget=forms.CheckboxSelectMultiple())
class Meta:
model = World
fields = ('name',)
But this code gives user possibility to choose objects and uncheck some. How to change it?
The best idea for me is when user creates his "world" - he can only type a name of world and he didn't see names of countries but every object "Country" will be includes in his "world" . Is that possible? How can i change code in forms to do something like this?
I think we could achieve that by overriding the ModelForm.save() method:
class WorldForm(forms.ModelForm):
class Meta:
model = World
fields = ('name',)
def save(self, commit=True):
super(WorldForm, self).save(commit=False)
is_new = not self.instance.pk
self.instance.save()
if is_new:
# It's a new world. Fill it with countries!
self.instance.country.add(*Country.objects.all())
else:
self._save_m2m()
return self.instance

how to show a django ModelForm field as uneditable

taking my initial lessons with django ModelForm ,I wanted to give the user ,ability to edit an entry in a blog.The BlogEntry has a date,postedTime, title and content.I want to show the user an editform which shows all these fields,but with only title and content as editable. The date and postedTime should be shown as uneditable.
class BlogEntry(models.Model):
title = models.CharField(unique=True,max_length=50)
description = models.TextField(blank=True)
date = models.DateField(default=datetime.date.today)
postedTime = models.TimeField(null=True)
...
For adding an entry ,I use a ModelForm in the normal way..
class BlogEntryAddForm(ModelForm):
class Meta:
model = BlogEntry
...
But how do I create the edit form?I want it to show the date,postedTime as uneditable (but still show them on the form) and let the user edit the title and description.
if I use,exclude in class Meta for date and postedTime,that will cause them not to appear on the form.So,how can I show them as uneditable?
class BlogEntryEditForm(ModelForm):
class Meta:
model = BlogEntry
...?...
In the form object, declare the attribute of the field as readonly:
form.fields['field'].widget.attrs['readonly'] = True
Is date field represent a date when the entry first created or when it was modified last time? If first then use auto_now_add option else use auto_now. That is:
date = models.DateField(auto_now_add=True)
will set date to now when entry will be created.
auto_now_add makes field uneditable. For other cases use editable option to make any field uneditable. For example
postedDate = models.TimeField(null=True, editable=False)
Also, likely you will add posted boolean field to Entry model, so it is convinient to set auto_now on postedDate. It will set postedDate to now every time you modify a Entry including one when you set posted to True.
I implemented it this way: https://djangosnippets.org/snippets/10514/
this implementation uses the data of model instance for all read-only fields and not the data obtained while processing the form
below the same code but using his example
from __future__ import unicode_literals
from django.utils import six
from django.utils.encoding import force_str
__all__ = (
'ReadOnlyFieldsMixin',
'new_readonly_form_class'
)
class ReadOnlyFieldsMixin(object):
"""Usage:
class MyFormAllFieldsReadOnly(ReadOnlyFieldsMixin, forms.Form):
...
class MyFormSelectedFieldsReadOnly(ReadOnlyFieldsMixin, forms.Form):
readonly_fields = ('field1', 'field2')
...
"""
readonly_fields = ()
def __init__(self, *args, **kwargs):
super(ReadOnlyFieldsMixin, self).__init__(*args, **kwargs)
self.define_readonly_fields(self.fields)
def clean(self):
cleaned_data = super(ReadOnlyFieldsMixin, self).clean()
for field_name, field in six.iteritems(self.fields):
if self._must_be_readonly(field_name):
cleaned_data[field_name] = getattr(self.instance, field_name)
return cleaned_data
def define_readonly_fields(self, field_list):
fields = [field for field_name, field in six.iteritems(field_list)
if self._must_be_readonly(field_name)]
map(lambda field: self._set_readonly(field), fields)
def _all_fields(self):
return not bool(self.readonly_fields)
def _set_readonly(self, field):
field.widget.attrs['disabled'] = 'true'
field.required = False
def _must_be_readonly(self, field_name):
return field_name in self.readonly_fields or self._all_fields()
def new_readonly_form_class(form_class, readonly_fields=()):
name = force_str("ReadOnly{}".format(form_class.__name__))
class_fields = {'readonly_fields': readonly_fields}
return type(name, (ReadOnlyFieldsMixin, form_class), class_fields)
Usage:
class BlogEntry(models.Model):
title = models.CharField(unique=True,max_length=50)
description = models.TextField(blank=True)
date = models.DateField(default=datetime.date.today)
postedTime = models.TimeField(null=True)
# all fields are readonly
class BlogEntryReadOnlyForm(ReadOnlyFieldsMixin, forms.ModelForm):
class Meta:
model = BlogEntry
# selected fields are readonly
class BlogEntryReadOnlyForm2(ReadOnlyFieldsMixin, forms.ModelForm):
readonly_fields = ('date', 'postedTime')
class Meta:
model = BlogEntry
or use the function
class BlogEntryForm(forms.ModelForm):
class Meta:
model = BlogEntry
BlogEntryFormReadOnlyForm = new_readonly_form_class(BlogEntryForm, readonly_fields=('description', ))
This will prevent any user from hacking the request:
self.fields['is_admin'].disabled = True
Custom form example:
class MemberShipInlineForm(forms.ModelForm):
is_admin = forms.BooleanField(required=False)
def __init__(self, *args, **kwargs):
super(MemberShipInlineForm, self).__init__(*args, **kwargs)
if 'instance' in kwargs and kwargs['instance'].is_group_creator:
self.fields['is_admin'].disabled = True
class Meta:
model = MemberShip
fields = '__all__'
From the documentation,
class BlogEntryEditForm(ModelForm):
class Meta:
model = BlogEntry
readonly_fields = ['date','postedTime']

How do I filter values in a Django form using ModelForm?

I am trying to use the ModelForm to add my data. It is working well, except that the ForeignKey dropdown list is showing all values and I only want it to display the values that a pertinent for the logged in user.
Here is my model for ExcludedDate, the record I want to add:
class ExcludedDate(models.Model):
date = models.DateTimeField()
reason = models.CharField(max_length=50)
user = models.ForeignKey(User)
category = models.ForeignKey(Category)
recurring = models.ForeignKey(RecurringExclusion)
def __unicode__(self):
return self.reason
Here is the model for the category, which is the table containing the relationship that I'd like to limit by user:
class Category(models.Model):
name = models.CharField(max_length=50)
user = models.ForeignKey(User, unique=False)
def __unicode__(self):
return self.name
And finally, the form code:
class ExcludedDateForm(ModelForm):
class Meta:
model = models.ExcludedDate
exclude = ('user', 'recurring',)
How do I get the form to display only the subset of categories where category.user equals the logged in user?
You can customize your form in init
class ExcludedDateForm(ModelForm):
class Meta:
model = models.ExcludedDate
exclude = ('user', 'recurring',)
def __init__(self, user=None, **kwargs):
super(ExcludedDateForm, self).__init__(**kwargs)
if user:
self.fields['category'].queryset = models.Category.objects.filter(user=user)
And in views, when constructing your form, besides the standard form params, you'll specify also the current user:
form = ExcludedDateForm(user=request.user)
Here example:
models.py
class someData(models.Model):
name = models.CharField(max_length=100,verbose_name="some value")
class testKey(models.Model):
name = models.CharField(max_length=100,verbose_name="some value")
tst = models.ForeignKey(someData)
class testForm(forms.ModelForm):
class Meta:
model = testKey
views.py
...
....
....
mform = testForm()
mform.fields["tst"] = models.forms.ModelMultipleChoiceField(queryset=someData.objects.filter(name__icontains="1"))
...
...
Or u can try something like this:
class testForm(forms.ModelForm):
class Meta:
model = testKey
def __init__(self,*args,**kwargs):
super (testForm,self ).__init__(*args,**kwargs)
self.fields['tst'].queryset = someData.objects.filter(name__icontains="1")
I know this is old; but its one of the first Google search results so I thought I would add how I found to do it.
class CustomModelFilter(forms.ModelChoiceField):
def label_from_instance(self, obj):
return "%s %s" % (obj.column1, obj.column2)
class CustomForm(ModelForm):
model_to_filter = CustomModelFilter(queryset=CustomModel.objects.filter(active=1))
class Meta:
model = CustomModel
fields = ['model_to_filter', 'field1', 'field2']
Where 'model_to_filter' is a ForiegnKey of the "CustomModel" model
Why I like this method:
in the "CustomModelFilter" you can also change the default way that the Model object is displayed in the ChoiceField that is created, as I've done above.
is the best answer:
BookDemoForm.base_fields['location'] = forms.ModelChoiceField(widget=forms.Select(attrs={'class': 'form-control select2'}),queryset=Location.objects.filter(location_for__fuel=True))

Categories