Autopopulate a set of generic many to many fields in Django? - python

I'm trying to combine this answer and this one, with a bit of for looping.
On creating a character, I want to add all possible skills with a value of 0 but I'm getting confused on how to follow the above answers.
I have this mixin:
class CrossCharacterMixin(models.Model):
cross_character_types = models.Q(app_label='mage', model='mage')
content_type = models.ForeignKey(ContentType, limit_choices_to=cross_character_types,
null=True, blank=True)
object_id = models.PositiveIntegerField(null=True)
content_object = GenericForeignKey('content_type', 'object_id')
class Meta:
abstract = True
(eventually, the cross_character_types will be expanded)
And this model:
class CharacterSkillLink(Trait, CrossCharacterMixin):
PRIORITY_CHOICES = (
(1, 'Primary'), (2, 'Secondary'), (3, 'Tertiary')
)
skill = models.ForeignKey('SkillAbility')
priority = models.PositiveSmallIntegerField(
choices=PRIORITY_CHOICES, default=None)
speciality = models.CharField(max_length=200, null=True, blank=True)
def __str__(self):
spec_string = " (" + self.speciality + ")" if self.speciality else ""
return self.skill.skill.label + spec_string
What I've started writing is this, on the NWODCharacter model:
def save(self, *args, **kwargs):
if not self.pk:
character_skills_through = CharacterSkillLink.content_object.model
CharacterSkillLink.objects.bulk_create([
[character_skills_through(skill=SkillAbility(
skill), content_object=self) for skill in SkillAbility.Skills]
])
super(NWODCharacter, self).save(*args, **kwargs)
This doesn't work as I don't think I'm passing in the right objects.
Based on this answer though:
from django.db import models
class Users(models.Model):
pass
class Sample(models.Model):
users = models.ManyToManyField(Users)
Users().save()
Users().save()
# Access the through model directly
ThroughModel = Sample.users.through
users = Users.objects.filter(pk__in=[1,2])
sample_object = Sample()
sample_object.save()
ThroughModel.objects.bulk_create([
ThroughModel(users_id=users[0].pk, sample_id=sample_object.pk),
ThroughModel(users_id=users[1].pk, sample_id=sample_object.pk)
])
In this situation, what is my ThroughModel? Is it CharacterSkillLink.content_object.model ?
How do I do this in my scenario? I'm sorry if this is trivial, but I'm struggling to get my head round it.

It looks to me like CharacterSkillLink itself is your through model in this case... it generically joins a content type to a SkillAbility
If you think about it, it also makes sense that if you're doing a bulk_create the objects that you pass in must be of the same model you're doing a bulk_create on.
So I think you want something like this:
def save(self, *args, **kwargs):
initialise_skill_links = not self.pk
super(NWODCharacter, self).save(*args, **kwargs)
if initialise_skill_links:
CharacterSkillLink.objects.bulk_create([
CharacterSkillLink(
skill=SkillAbility.objects.get_or_create(skill=skill)[0],
content_object=self
)
for skill in SkillAbility.Skills
])
Note you had too many pairs of [] inside your bulk_create.
Also I think you should use SkillAbility.objects.get_or_create()... for a foreign key you need the related object to exist. Just doing SkillAbility() won't fetch it from the db if it already exists and won't save it to the db if it doesn't.

Related

Django models default value based on parent length

I'm making an app that has multiple exams and multiple questions for each exam.
This is my current 'Question' model:
class Question(models.Model):
exam = models.ForeignKey(Exam, related_name='questions', on_delete=models.CASCADE)
question = models.TextField()
explanation = models.TextField(blank=True, null=True)
TOPICS = [
('NA', 'Not Available'),
('Algebra', 'Algebra'),
('Geometry', 'Geometry'),
('Trig', 'Trigonometry'),
('Calc', 'Calculus'),
('Chem', 'Chemistry'),
('Geology', 'Geology'),
('Physics', 'Physics'),
('Reading', 'Reading'),
('Writing', 'Writing'),
('Spelling', 'Spelling'),
('Comprehension', 'Reading Comprehension'),
]
topic = models.CharField(max_length=20, choices=TOPICS, default='NA')
order = models.IntegerField(default=0)
created = models.DateTimeField(auto_now_add=True)
attempts = models.IntegerField(default=0, editable=False)
correct_attempts = models.IntegerField(default=0, editable=False)
class Meta:
unique_together = ['exam', 'order']
def __str__(self):
return f'{self.exam} - Q{self.order}'
You can pretty much ignore all the fields except the 'order' field. This field shows what order the question will appear on the exam.
I would like for the default value of this order field to be the number of existing questions in the exam + 1.
For example, if my exam has two questions in it already, and I'm trying to add a third question, the order of this question will default to '3' unless I manually change it.
I know this doesn't work, but this solution would work similarly to this line of code:
default=Question.objects.filter(exam=self.exam).count() + 1
I'm inexperienced in creating functions for models in django so please let me know how I would do something like this, thanks!
I solved this by overriding the save() function with this:
def save(self, *args, **kwargs):
self.order = Question.objects.filter(exam=self.exam).count() + 1
super().save(*args, **kwargs) # Call the "real" save() method.

How to use model objects as choices for django-filter MultipleChoiceFilter

I'm using Django-filter, and I would like one of the fields (supervisor) to be a ChoiceFilter where the choices are objects from the model. What's the most efficient way to do that? I tried following this post, but kept getting errors no matter what I changed (currently cannot unpack non-iterable int object).
# models.py
class people(models.Model):
namelast = models.CharField(max_length=100, verbose_name='Last Name')
namefirst = models.CharField(max_length=100, verbose_name='First Name')
supervisor = models.ForeignKey('self', blank=True, null=True, on_delete=models.SET_NULL, verbose_name='Supervisor')
def __str__(self):
return "%s %s" % (self.namefirst, self.namelast)
# filters.py
class office_filter(django_filters.FilterSet):
supervisor = django_filters.ChoiceFilter(choices=[], lookup_expr='icontains', label='Supervisor')
# other fields
class Meta:
model = people
fields = ['namelast', 'namefirst', 'supervisor']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
try:
self.filters['supervisor'].extra['choices'] = [x for x in
people.objects.all().values_list('supervisor', flat=True).distinct()]
except (KeyError, AttributeError):
pass
The goal is to have the supervisor field be a nice menu of all of the people that have been added assigned as a supervisor in the people model.
I am not a 100% sure, but could you try something?
[people.objects.get(pk=x[0]) for x in people.objects.all().values_list('supervisor', flat=True).distinct()]
or
[people.objects.get(id=x[0]) for x in people.objects.all().values_list('supervisor', flat=True).distinct()]
In the link that you mentioned I believe
DatedResource.objects.all().values_list('date', flat=True).distinct())
returns an array of string
In your code
people.objects.all().values_list('supervisor', flat=True).distinct()
would return a string of int - the IDs of the records as it is a Foreign Key
Ananya's answer helped me get on the correct track of what have that statement return, but after thinking about the errors and how choice lists are usually constructed, I realized I needed it to return a tuple (not just a value). Here is the relevant code that ended up working:
class office_filter(django_filters.FilterSet):
supervisor = django_filters.ChoiceFilter(choices=[], label='Supervisor')
#...
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
try:
self.filters['supervisor'].extra['choices'] = [(people.objects.get(pk=x).id, people.objects.get(pk=x)) for x in people.objects.all().values_list('supervisor', flat=True).distinct() if x is not None]
except (KeyError, AttributeError):
pass
The important part being: (people.objects.get(pk=x).id, people.objects.get(pk=x)) rather than just people.objects.get(pk=x).
Also had to remove lookup_expr in the filter field.

Significant performance issue with Django Admin - foreign key labels

I’m experience significant performance issue with Django Admin.
I have a mapping model where I map primary keys of 2 other modes
In my FundManagerMappingAdmin I try to represent the foreign key of the 2 tables with a label from the foreign key models.
The underlying models are about 4000 lines
I’m experiencing slow performance when retrieving the list in admin and then also when editing and updating
Could someone please point out the inefficiencies in this code?
Is there a better way please?
Admin.py
#admin.register(ChampDwDimFundManagerMapping)
class FundManagerMappingAdmin(admin.ModelAdmin):
list_display = ['get_champ_fund_manager_id', 'get_evestment_name', 'get_sharepoint_name', ]
def get_champ_fund_manager_id(self, obj):
return obj.fund_manager_id
get_champ_fund_manager_id.short_description = 'CHAMP Manager ID'
def get_evestment_name(self, obj):
return obj.evestment_fund_manager_id.manager_name
get_evestment_name.short_description = 'Evestment Manager Name'
def get_sharepoint_name(self, obj):
return obj.sharepoint_fund_manager_id.manager_name
get_sharepoint_name.short_description = 'Sharepoint Manager Name'
def get_form(self, request, obj=None, **kwargs):
form = super(ChampFundManagerMappingAdmin, self).get_form(request, obj, **kwargs)
form.base_fields['sharepoint_fund_manager_id'].label_from_instance = lambda obj: "{} {}".format(obj.final_publications_fund_manager_id, obj.manager_name)
form.base_fields['evestment_fund_manager_id'].label_from_instance = lambda obj: "{} {}".format(obj.evestment_fundmanager_id_bk, obj.manager_name)
return form
Models.py
class FundManagerMapping(models.Model):
fund_manager_id = models.AutoField(db_column='FundManagerId', primary_key=True)
sharepoint_fund_manager_id = models.ForeignKey(SharePointFundManager, models.DO_NOTHING, db_column='SharePointFundManagerId')
evestment_fund_manager_id = models.ForeignKey(EvestmentFundManager, models.DO_NOTHING, db_column='eVestmentFundManagerId')
class EvestmentFundManager(models.Model):
evestment_fund_manager_id = models.AutoField(db_column='eVestmentFundManagerId', primary_key=True)
package_execution_id = models.IntegerField(db_column='PackageExecutionId')
evestment_fund_manager_id_bk = models.CharField(db_column='eVestmentFundManagerId_BK', max_length=50)
manager_name = models.CharField(db_column='ManagerName', max_length=255)
class SharePointFundManager(models.Model):
sharepoint_fund_manager_id = models.AutoField(db_column='SharePointFundManagerId', primary_key=True)
package_execution_id = models.IntegerField(db_column='PackageExecutionId')
research_fund_manager_id = models.CharField(db_column='ResearchFundManagerId', max_length=50, blank=True, null=True)
final_publications_fund_manager_id = models.CharField(db_column='FinalPublicationsFundManagerId', max_length=50, blank=True, null=True)
manager_name = models.CharField(db_column='ManagerName', max_length=255)
You are showing the name of related entities (because of get_evestment_name and get_sharepoint_name) without joining/prefetching them. That means for every row that you display and every name of the related entity it requires django to make a database query. You need to override get_queryset() of the ModelAdmin and use select_related to tell django to join those entities from the beginning so that it does not need any additional queries to get those names:
#admin.register(ChampDwDimFundManagerMapping)
class FundManagerMappingAdmin(admin.ModelAdmin):
def get_queryset(self, request):
return super().get_queryset(request).select_related(
'sharepoint_fund_manager_id',
'evestment_fund_manager_id',
)
Also you don't name ForeignKey fields something_id. It is just sharepoint_fund_manager because what you get when you call fund_manager.sharepoint_fund_manager_id is not an id but an instance of SharePointFundManager. It is weird to call sharepoint_fund_manager_id.name. An id does not have a name attribute. A fund manager has.
Additionally Django does automatically create a property sharepoint_fund_manager_id for you if you call the field sharepoint_fund_manager to access the plain foreign key.

A for loop in forms.py fails to load data immediately after it's sent | Python Django

In the forms.py I have a short piece of code which loads the data, but only after I edit print("hello") in it.
The code as follows:
models.py:
class CreateAssignment(models.Model):
user = models.ForeignKey(User, editable=False, blank=True, null=True)
progress = models.CharField(max_length=254, editable=False, blank=True, null=True)
class SetAssignment(models.Model):
mechanic = models.ForeignKey(User, editable=False, blank=True, null=True)
assignment = models.IntegerField(blank=True, null=True)
The mechanic is a permission, plus this mechanic's ID will show in the url of the website, when you will try to set an assignment for this mechanic.
forms.py:
class SetAssignmentForm(forms.ModelForm):
ASSIGNMENT_CHOICES = ()
for item in CreateAssignment.objects.all():
if item.progress == 'Scheduling':
user = User.objects.get(id=item.user_id).username
ASSIGNMENT_CHOICES += (
(item.id, user + ' - ' + str(item.id)),
)
assignment = forms.ChoiceField(choices=ASSIGNMENT_CHOICES, help_text='This is the assignment that you want to apply to this mechanic.')
class Meta:
model = SetAssignment
fields = ('assignment', )
The user_id in this situation is the user that has been set in the CreateAssignment model.
Now the issue is:
The for loop in the SetAssignmentForm works, but it loads data after I put a print in it or when I remove the print from it. Which of course shouldn't really affect the code.
Is there something I'm overlooking? I've been programming in Python Django for 8 weeks now, so if this is a basic program failure, please refer me to a page, because I haven't been able to find any information on this issue.
Thanks for the help.
For the ones that want to know:
views.py:
#login_required
def set_assignment(request):
form = SetAssignmentForm()
id = request.GET.get('id')
user_results = User.objects.filter(pk=id).values()
return render(request, 'pages/set_assignment.html', {'form': form, 'user_results': user_results})
Gif so you can visually see what's happening:
https://drive.google.com/open?id=1u7gfdiS7KitQWNVuvQEEOFJ9wD3q9rY6
You must not write code like this at class level. Anything at that level is only executed once, at definition time - ie when the class is first imported.
If you need to make the values dynamic, you should put the logic inside the __init__ method:
class SetAssignmentForm(forms.ModelForm):
assignment = forms.ChoiceField(choices=[], help_text='This is the assignment that you want to apply to this mechanic.')
def __init__(self, *args, **kwargs):
super(SetAssignmentForm, self).__init__(*args, **kwargs)
items = CreateAssignment.objects.filter(progress='Scheduling').select_related('user')
choices = [(item.id, '{} - {}'.format(item.id, item.user.username)) for item in items]
self.fields['assignment'].choices = choices
(Note, your query logic was very inefficient; my code only hits the database one time.)
However, here you don't even need to do that, because Django already has a form field - ModelChoiceField - that takes its values from the database. You can use a custom subclass of that to show the representation:
class AssignmentField(forms.ModelChoiceField):
def label_from_instance(self, item):
return (item.id, '{} - {}'.format(item.id, item.user.username))
class SetAssignmentForm(forms.ModelForm):
assignment = forms.AssignmentField(queryset=CreateAssignment.objects.filter(progress='Scheduling').select_related('user'))

Django model foreignkey queries

So i have this two models in django:
class Course(models.Model):
def get_image_path(self, filename):
return os.path.join('courses', str(self.slug), filename)
def __str__(self):
return self.name
def save(self, *args, **kwargs):
self.slug = slugify(self.name)
super(Course, self).save(*args, **kwargs)
name = models.CharField(max_length=255, verbose_name="Nombre")
description = models.CharField(max_length=255, verbose_name="Descripción")
price = models.DecimalField(max_digits=12,decimal_places=2, verbose_name="Precio")
slug = models.SlugField(blank=True, max_length=255)
icon_img = models.ImageField(upload_to=get_image_path, blank=True, null=True, verbose_name="Imagen")
background_color = ColorField(default="#026085")
class Meta:
verbose_name = "curso"
verbose_name_plural = "cursos"
class UserCourse(models.Model):
user = models.ForeignKey(User)
course = models.ForeignKey(Course)
So whenever a user "buys" a course, it is stored in UserCourse. I have a view where the system shows a list of all the courses the user has bought. This is the view code:
def user_course_list_view(request, username):
context_dict = {}
try:
user_courses = UserCourse.objects.filter(user=request.user).course_set
context_dict['courses'] = user_courses
context_dict['heading'] = "Mis cursos"
except:
context_dict['courses'] = None
context_dict['heading'] = "Mis cursos wey"
return render(request, 'courses/course_list.html', context=context_dict)
I dont know where is the error and I cant seem to catch the exception (im using django with docker)
tl;dr
Something like this should work.
usercourse_objects = UserCourse.objects.filter(user=request.user).select_related('course')
user_courses = [x.course for x in usercourse_objects]
Explanation
There are multiple ways to do this, but one way would be to first get all the UserCourse objects for the current user:
usercourse_objects = UserCourse.objects.filter(user=request.user)
Then, for each UserCourse object, get the related Course:
user_courses = [x.course for x in usercourse_objects]
Now, the second line causes N database queries (one for each time we follow the course foreign key relation. To prevent this, the first line can be changed to:
usercourse_objects = UserCourse.objects.filter(user=request.user).select_related('course')
This pre-populates the course attribute of the UserCourse objects. More info about select_related() can be found here.

Categories

Resources