Django: Suggestion for models design - python

I need help with creating models for my simple Django app.
The purpose of the application is to let users (referees) register for matches, then admin will choose 2 users (referees) from the list of registered for given match. Right now my Matches model looks like below:
class Match(models.Model):
match_number = models.CharField(
max_length=10
)
home_team = models.ForeignKey(
Team,
on_delete=models.SET_NULL,
null=True,
related_name='home_team'
)
away_team = models.ForeignKey(
Team,
on_delete=models.SET_NULL,
null=True,
related_name='away_team'
)
match_category = models.ForeignKey(
MatchCategory,
on_delete=models.SET_NULL,
null=True
)
date_time = models.DateTimeField(
default=timezone.now
)
notes = models.TextField(
max_length=1000,
blank=True
)
What I thought to do is to create new Model named MatchRegister where I will be saving match_id and user_id, something like below:
class MatchRegister(models.Model):
match_id = models.ForeignKey(
Match
)
user_id = models.ForeignKey(
Users
)
And than admin will have list of registered user for given match from which he will choose two, so I thought to modify my Match model like this (add two new Fields):
class Match(models.Model):
match_number = models.CharField(
max_length=10
)
home_team = models.ForeignKey(
Team,
on_delete=models.SET_NULL,
null=True,
related_name='home_team'
)
away_team = models.ForeignKey(
Team,
on_delete=models.SET_NULL,
null=True,
related_name='away_team'
)
match_category = models.ForeignKey(
MatchCategory,
on_delete=models.SET_NULL,
null=True
)
date_time = models.DateTimeField(
default=timezone.now
)
notes = models.TextField(
max_length=1000,
blank=True
)
ref_a = models.ForeignKey(
Users,
on_delete=models.SET_NULL,
null=True,
related_name='ref_a'
)
ref_b = models.ForeignKey(
Users,
on_delete=models.SET_NULL,
null=True,
related_name='ref_b'
)
This is my solution but I don't know if it is done in proper way so I want to ask you for help.

If you know for certain that matches will only ever have two refs, then what you propose is just fine. However, if there's an opportunity in the future for the number to change (only one, or perhaps three), an alternative would be to add a flag to the intermediate table:
class MatchRegister(models.Model):
match_id = models.ForeignKey(Match)
user_id = models.ForeignKey(Users)
chosen = models.BooleanField(default=False)
You would need business logic to constrain the number of "chosen" refs to the number you anticipate. This option makes it easy to increase or decrease the number of refs without adding or removing columns (just change the business logic).

Related

Django Model Instance as Template for Another Model that is populated by Models

I'm trying to create a workout tracking application where a user can:
Create an instance of an ExerciseTemplate model from a list of available Exercise models. I've created these as models so that the user can create custom Exercises in the future. There is also an ExerciseInstance which is to be used to track and modify the ExerciseTemplate created by the user, or someone else. I'm stripping the models of several unimportant fields for simplicity, but each contains the following:
class Exercise(models.Model):
# Basic Variables
name = models.CharField(max_length=128)
description = models.TextField(null=True, blank=True)
def __str__(self):
return self.name
class ExerciseTemplate(models.Model):
# Foreign Models
workout = models.ForeignKey(
'Workout',
on_delete=models.SET_NULL,
null=True,
blank=True
)
exercise = models.ForeignKey(
Exercise,
on_delete=models.SET_NULL,
null=True,
blank=True
)
recommended_sets = models.PositiveIntegerField(null=True, blank=True)
class ExerciseInstance(models.Model):
""" Foreign Models """
exercise_template = models.ForeignKey(
ExerciseTemplate,
on_delete=models.SET_NULL,
null=True,
blank=True
)
workout = models.ForeignKey(
'Workout',
on_delete=models.SET_NULL,
null=True,
blank=True
)
""" Fields """
weight = models.PositiveIntegerField(null=True, blank=True)
reps = models.PositiveIntegerField(null=True, blank=True)
Create a WorkoutInstance from a WorkoutTemplate. The WorkoutTemplate is made up of ExerciseTemplates. But the WorkoutInstance should be able to take the WorkoutTemplate and populate it with ExerciseInstances based on the ExerciseTemplates in the WorkoutTemplate. Here are the models that I have so far:
class WorkoutTemplate(models.Model):
name = models.CharField(max_length=128)
description = models.TextField(null=True, blank=True)
date = models.DateTimeField(null=True, blank=True)
#category...
exercises = models.ManyToManyField(
Exercise,
through=ExerciseTemplate
)
def __str__(self):
return self.name
class WorkoutInstance(models.Model):
# Foreign Models
workout_template = models.ForeignKey(
'WorkoutTemplate',
on_delete=models.SET_NULL,
null=True,
blank=True
)
But this is where I get stuck. I'm not sure how to proceed. My intuition is one of the following:
I need to create a more simple architecture to do this. I'll take any suggestions.
I need to create a method within the model that solves this issue. If this is the case, I'm not sure what this would actually look like.
When you create a new WorkoutInstance object which references a given WorkoutTemplate object you get all its related ExerciseTemplate objects.
Then you just create a new object (row) for each ExerciseInstance in another model (table)
If you link your ExerciseInstance to WorkoutInstance via 'workout' you could do something like:
wt = WorkoutTemplate.get(id=1)
wi = WorkoutInstance.create(workout_template=wt)
for e in wt.exercisetemplate_set.all:
ExerciseInstance.create(exercise_template=e, workout=wi)
You can implent this in the method that creates the new WorkoutInstance or take a look at signals
https://docs.djangoproject.com/en/4.0/topics/db/optimization/#create-in-bulk

Django: Filtering via SQL, not Python

I created the following context variables context["genders"] and context["ages"].
Currently, there is a lot of work done by Python under #Filtering, while I think it would be better done in #Query.
However, that's where I currently struggle. Do you have an idea on how to achieve the pre-filtering in the #Query section via SQL?
Please not the int(answer_obj.answer) as answer is a TextField.
# Query
responses = Response.objects.filter(
survey__event=12, survey__template=settings.SURVEY_POST_EVENT
).order_by("-created")
# Filtering
filtered_responses = []
for response in responses:
for answer_obj in response.answers.all():
if (
answer_obj.question.focus == QuestionFocus.RECOMMENDATION_TO_FRIENDS
and int(answer_obj.answer) >= 8
):
filtered_responses.append(response)
# Context
gender_list = []
age_list = []
for response in filtered_responses:
for answer_obj in response.answers.all():
# Here a list of all the genders that gave that answer:
if answer_obj.question.focus == QuestionFocus.GENDER:
gender_list.append(answer_obj.answer)
# Here a list of all the ages that gave that answer:
if answer_obj.question.focus == QuestionFocus.AGE:
age_list.append(answer_obj.answer)
context["genders"] = gender_list
context["ages"] = age_list
models.py
class Answer(TimeStampedModel):
question = models.ForeignKey(
"surveys.Question", on_delete=models.CASCADE, related_name="answers"
)
response = models.ForeignKey(
"Response", on_delete=models.CASCADE, related_name="answers"
)
answer = models.TextField(verbose_name=_("Answer"))
choices = models.ManyToManyField(
"surveys.AnswerOption", related_name="answers", blank=True
)
class Response(TimeStampedModel):
class Language(Choices):
CHOICES = settings.LANGUAGES
survey = models.ForeignKey(
"surveys.Survey", on_delete=models.CASCADE, related_name="responses"
)
order = models.ForeignKey(
"orders.Order",
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="response",
)
attendee = models.ForeignKey(
"attendees.Attendee",
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="response",
)
total_time = models.PositiveIntegerField(
null=True, blank=True, verbose_name=_("Total time")
)
ip_address = models.GenericIPAddressField(null=True, verbose_name=_("IP Address"))
language = models.CharField(
max_length=Language.get_max_length(),
choices=Language.CHOICES,
verbose_name=_("Language"),
)
class Question(TimeStampedModel):
survey = models.ForeignKey(
"surveys.Survey", on_delete=models.CASCADE, related_name="questions"
)
question_set = models.ForeignKey(
"QuestionSet", on_delete=models.CASCADE, related_name="questions"
)
title = models.CharField(max_length=100, verbose_name=_("Title"))
help_text = models.TextField(null=True, blank=True, verbose_name=_("Help text"))
type = models.CharField(
max_length=QuestionType.get_max_length(),
choices=QuestionType.CHOICES,
verbose_name=_("Question type"),
)
focus = models.CharField(
max_length=QuestionFocus.get_max_length(),
choices=QuestionFocus.CHOICES,
verbose_name=_("Question focus"),
)
required = models.BooleanField(default=False, verbose_name=_("Is required?"))
position = models.PositiveSmallIntegerField(
null=True, blank=True, verbose_name=_("Position")
)
# Translatable fields
i18n = TranslationField(fields=("title", "help_text"))
class Meta:
ordering = ("position", "pk")
Filtering
It looks to me, that you want all responses, where answer is higher than eight for question focused on friend recommendation. Is it expected that you might have the same response appended to the filtered responses more than once, or will there be only one question of this type? I think you could rewrite it as follows:
filtered_response = responses.filter(
answers__question__focus=QuestionFocus.RECOMMENDATION_TO_FRIENDS
).annotate(
answer_num=Cast("answers__answer", IntegerField()),
).filter(
answer_num__gt=8,
)
And populating the context:
context["genders"] = Answer.objects.filter(
response_id__in=filtered_response.values_list("id", flat=True),
question__focus=QuestionFocus.GENDER,
).values_list("answer", flat=True)
context["ages"] = Answer.objects.filter(
response_id__in=filtered_response.values_list("id", flat=True),
question__focus=QuestionFocus.AGE,
).values_list("answer", flat=True)
This should allow you to avoid firing the queries to iterate over response.answers.all(), and hopefully achieve the same result.

How to create two choices fields by using foreign key from another model?

I want to create a model which will hold the routes between two places, but I don't know how to handle choices fields in this model, also my choices should hold only places (village, city, and town)
my Place model:
class Place(CoreModel):
TOWN = 'town'
CITY = 'city'
REGION = 'region'
DISTRICT = 'district'
VILLAGE = 'village'
ROLE_CHOICES = (
(REGION, 'область'),
(CITY, 'місто'),
(DISTRICT, 'район'),
(TOWN, 'село міського типу'),
(VILLAGE, 'село')
)
name = models.CharField(max_length=128, verbose_name='Place name', )
slug = models.SlugField(max_length=128, blank=True, null=True)
parent = models.ForeignKey('self', on_delete=models.CASCADE, blank=True, null=True)
role = models.CharField(max_length=20, choices=ROLE_CHOICES, null=True, blank=True)
my Route model:
class Routes(CoreModel):
start_point = models.ForeignKey(Place, on_delete=models.CASCADE)
end_point = models.ForeignKey(Place, on_delete=models.CASCADE)
but it doesn't work
When you have a model with two ForeignKeys to the same model, you must give at least one of them a related_name otherwise django doesn't know how to differentiate them in the reverse relationship:
class Routes(CoreModel):
start_point = models.ForeignKey(Place, on_delete=models.CASCADE, related_name="departing_routes")
end_point = models.ForeignKey(Place, on_delete=models.CASCADE, related_name="arriving_routes")

Django query get objects that are not in other object

I have two Django models: Match and MatchRegister. I want to get list of all Matches that are not in MatchRegister object but I'm unable to do so. Could you please help me achieve it?
Below my two classes
class Match(models.Model):
"""Model representing Match object"""
match_number = models.CharField(
max_length=10
)
home_team = models.ForeignKey(
Team,
on_delete=models.SET_NULL,
null=True,
related_name='home_team'
)
away_team = models.ForeignKey(
Team,
on_delete=models.SET_NULL,
null=True,
related_name='away_team'
)
match_category = models.ForeignKey(
MatchCategory,
on_delete=models.SET_NULL,
null=True
)
date_time = models.DateTimeField(
default=timezone.now
)
notes = models.TextField(
max_length=1000,
blank=True
)
last_update = models.DateTimeField(
auto_now=timezone.now
)
class MatchRegister(models.Model):
match = models.ForeignKey(
Match,
on_delete=models.SET_NULL,
null=True
)
You can use the __isnull filter with the correct related_query_name (which defaults to the lower case model name):
Match.objects.filter(matchregister__isnull=True)
You could take list of all the matches connected with a match register and then exclude it from the matches altogether, like this:
all_match_registers = MatchRegister.objects.all()
ids_to_exclude = []
for match_register in all_match_registers:
ids_to_exclude.append(match_register.match.id)
Match.objects.exclude(id__in = ids_to_exclude)

Django drag-n-drop form validation

DJANGO 2.0
So basically I was tasked to make a drag and drop form, in which I would have a table of available users, and create a team from that table, by dragging and dropping the users.
My problem is, that I have no idea where to start tackling this problem, mostly because I'm not sure how to validate the data that is dropped after I hit submit.
So my goals are:
Validate drag-n-drop data
Add a user status so it won't be reassigned to another team.
Assign credits to users upon competition of the given task (this is used for making reports and to add an "achievements" functionalities)
Anything leading to drag-n-drop validation in django will be helpful too
My models.py for users
class Employee(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
department = models.CharField(
max_length=100,
default=''
)
title = models.CharField(
max_length=60,
default=''
)
date_joined = models.DateField(
default=datetime.date.today
)
avatar = models.ImageField(
upload_to="avatars"
)
My models.py for creating the teams, (I still need to write a Foreign Key or something to store the users that will be on the team):
class Task(models.Model):
Task_name = models.CharField(
max_length=15,
default=""
)
Task_date = models.DateField()
Task_deadline = models.DateField()
Task_credits = models.DecimalField(
max_digits=2,
decimal_places=0
)
Task_reference = models.CharField(
max_length=100,
default=''
)
Task_description = models.TextField(
max_length=3000
)
Task_notes = models.TextField(
max_length=500
)
Task_completition = models.CharField(
max_length=15,
default=_lazy('Assigned')
choices=
)
Task_tags = models.CharField(
max_length=20,
default='',
)

Categories

Resources