Yes, this is an assignment, and yes, I've spent some time on it and now I need help.
My task has two models, Server and Client and they are in 1-N relationship. as noted below
# models.py
class Server(models.Model):
name = models.CharField(max_length=255, unique=True, null=False, blank=False)
maximum_clients = models.IntegerField(default=1,null=False, blank=False)
class Client(models.Model):
name = models.CharField(max_length=255, unique=True, null=False, blank=False)
active = models.BooleanField(default=True)
server = models.ForeignKey(Server)
I have created a form with ModelForm which allows me to create a new client on a given server, but the prerequisite of the task is to only offer servers which have free capacity (their maximum_clients is less than actual clients) so this is what I did
#forms.py
from django.db.models import Count
qs = Server.objects.annotate(Count('client'))
server_choices = []
for server in qs:
if server.client__count < server.maximum_clients:
server_choices.append((server,server))
class ClientForm(forms.ModelForm):
name = forms.CharField(label='Client name')
server = forms.ChoiceField(choices=server_choices)
class Meta:
model = Client
fields = ('name','server',)
This approach populates the select with the right servers based on a precondition that I mentioned. However, saving this form produces an error like Cannot assign "u'fifty'": "Client.server" must be a "Server" instance. Fifty is the name of the server with maximum_clients = 50
There is a similar form on admin screens which I also modified to show only available servers and saving there produces the same error.
This is not the right approach. Apart from the error you are seeing, you will also find that your server_choices only update when you restart the webserver, rather than doing so whenever the Server objects themselves change.
You have a foreign key, and need to select from a subset of the related objects. The correct field for that is a ModelChoiceField; this takes a queryset which you can filter in the definition. Since your filter depends on a field in the same model, you need to use an F object.
class ClientForm(forms.ModelForm):
name = forms.CharField(label='Client name')
server = forms.ModelChoiceField(
queryset=Server.objects.annotate(client_count=Count('client')).filter(client_count__lt=F('maximum_clients')
)
Related
i have are question about "__in" lookup in Django ORM.
So here is the example of code:
tags = [Tag.objects.get(name="sometag")]
servers = Server.objects.filter(tags__in=tags)[offset_from:offset_to]
server_infos = []
for server in servers:
server_infos.append(query.last())
So here is the problem: we making about 60-70 sql requests for each server. But i want to do something like this:
tags = [Tag.objects.get(name="sometag")]
servers = Server.objects.filter(tags__in=tags)[offset_from:offset_to]
server_infos = ServerInfo.objects.filter(contains__in=servers)
assert servers.count() == server_infos.count()
Can i do this without raw sql request? All i need to understand is how to limit "__in" expression in Django to get only last value as in example above. Is it possible?
Update, my models:
class Tag(models.Model):
name = models.CharField(max_length=255, blank=True)
added_at = models.DateTimeField(auto_now_add=True, null=True)
def __str__(self):
return self.name
class Server(models.Model):
ip = models.CharField(max_length=255, blank=True)
port = models.IntegerField(blank=True)
name = models.CharField(max_length=255, blank=True)
tags = models.ManyToManyField(Tag)
added_at = models.DateTimeField(auto_now_add=True, null=True)
def __str__(self):
return self.name
def get_server_online(self):
query = ServerInfo.objects.filter(contains=self)
if query.exists():
return query.last().online
return 0
class ServerInfo(models.Model):
contains = models.ForeignKey(Server, \
on_delete=models.CASCADE, null=True, blank=True)
map = models.CharField(max_length=255, blank=True)
game = models.CharField(max_length=255, blank=True)
online = models.IntegerField(null=True)
max_players = models.IntegerField(null=True)
outdated = models.BooleanField(default=False)
tags = models.ManyToManyField(Tag)
ping = models.IntegerField(null=True)
def __str__(self):
return f"Current map {self.map} and current online {self.online}/{self.max_players}"
I think the problem is that tags is ManyToMany and a server may be selected twice by two different tags. Also that a server may have >1 serverinfos, because it's a ForeignKey relation not a OneToOne.
Possibilities:
Make sure a server is returned only once in the queryset:
servers = Server.objects.filter(tags__in=tags).distinct()[offset_from:offset_to]
(or distinct('pk') ?)
Make sure only one ServerInfo instance is returned per server:
server_infos = ServerInfo.objects.filter(contains__in=servers).distinct('contains')
Or use prefetch_related in the Servers query, and then avoid subsequent queries by always referring to the serverinfo objects through the related name (default "serverinfo_set")
I absolutely hate "magic" default related names, and would always code one explicitly: contains = models.ForeignKey(Server, ..., related_name='server_infos', ...)
servers = Server.objects.filter(tags__in=tags).distinct(
).prefetch_related('serverinfo')[offset_from:offset_to]
for server in servers:
server_info = server.serverinfo_set.first()
# or
for info in server.serverinfo_set:
NB don't start applying filters to serverinfo_set if you don't want to hit the DB N times. Filter by iterating through what is presumably a short list, and which is already in memory in the queryset anyway.
The following should retrieve the data using two queries, one to get all the servers and the second one to get all the server infos using prefetch_related(). When filtering on a ManyToManyField you have to use distinct() to avoid duplicate results.
servers = Server.objects.filter(
tags__name="sometag").distinct().prefetch_related("server_info_set")
for server in servers:
print(server.name)
for info in server.server_info_set.all():
print(info)
If you only want to retrieve one certain info per server you would have to provide a custom queryset to prefetch_related() using Prefetch() objects.
from django.db.models import Prefetch
servers = Server.objects.filter(
tags__name="sometag").distinct().prefetch_related(Prefetch('server_info_set',
queryset=ServerInfo.objects.filter(outdated=False),
to_attr="current_infos"
)
)
for server in servers:
print(server.name)
for info in server.current_infos.all():
print(info)
I'm working on a Django Rest project where I'm given two MySQL tables:
metrics: Contain a row for each potential metric
daily_data: Contains a row for each data entry where the column names refer to metrics from the 'metrics' table
What I want to do now, is creating new entries in 'metrics' which should be automatically added to existing 'daily_data' entries (with a default value) and displayed on the website.
Here is how the current models looks like:
class Metrics(model.Model):
metric_id = models.CharField(max_length=255, primary_key=True)
is_main_metric = models.BooleanField(default=False)
name = models.CharField(max_length=255, blank=False, null=False)
description = models.CharField(max_length=255, blank=False, null=False)
lower_bound = models.FloatField(default=0.0, null=False)
upper_bound = models.FloatField(default=0.0, null=False)
class Meta:
verbose_name_plural = "Metrics"
db_table = "metrics"
class DailyData(models.Model):
location = models.CharField(max_length=255, blank=False, null=False)
date = models.DateField(blank=False, null=False)
# then a static field for each metric is added that corresponds to a 'metric_id' in the table 'metrics':
metric_01 = models.FloatField(default=0.0, null=False)
metric_02 = models.FloatField(default=0.0, null=False)
metric_03 = models.FloatField(default=0.0, null=False)
...
class Meta:
verbose_name_plural = "Daily Data"
db_table = "daily_data"
Later on, the Javascript code iterates over all 'metrics' to display them with the corresponding values from a requested 'daily_data' entry. Here is a small example:
let resp = await axios.get(`${API_URL}/daily_data/?location=berlin&date=2021-01-07`);
let data = resp.data[0];
METRICS.forEach(metric => {
let name = metric.name;
let description = metric.description;
let value = data[metric.metric_id];
$content.append(
` <div class="row">
<span>${name}:</span>
<span>${value}</span>
<span>${description}"</span>
</div> `
);
...
}
For the case that all metrics are pre-defined, the application is running fine. If I want to add a new metric, I create a new row in the database table 'metrics', then add the field manually to the 'DailyData' model from above, and finally restart the server.
However, my problem now is that I need the possibility to add new metrics dynamically. I.e. if a user adds a new metric (for example with a POST request), the metric should be added as a column to all existing 'daily_data' entries and should be displayed as an additional field on the website.
The intention is basically something like this (I know that this won't work, but just to get the idea):
def onNewMetricCreation(newMetric):
metric_id = newMetric.metric_id
new_field = models.FloatField(default=0.0, null=False)
DailyData.appendField(metric_id, new_field)
Is there a way to achieve this and add these model fields dynamically? Or is my whole data structure faulty for this case?
Edit: To solve the problem I've actually changed my data structure a bit. I've added a MetricsData model that connects the DailyData with the Metrics and contains the corresponding values. This allows each DailyData object to have a different number of metrics and new ones can be added easily.
The new models look like this:
class DailyData(models.Model):
location = models.ForeignKey("Locations", on_delete=models.CASCADE, blank=False, null=False)
date = models.DateField(blank=False, null=False)
class MetricsData(models.Model):
data_entry = models.ForeignKey("DailyData", on_delete=models.CASCADE, related_name="data_entry")
metric = models.ForeignKey("Metrics", on_delete=models.CASCADE)
value = models.FloatField(default=0.0, null=False)
class Metrics(models.Model):
metric_id = models.CharField(max_length=255, primary_key=True)
...
If I understood you correct I belive you're looking for a ForeignKey(). You would add this to your model:
class DailyData(models.Model):
metrics = models.ForeignKey(Metrics, on_delete=models.CASCADE)
Go inside django admin and I think you'll understand how ForeignKeys work. It's a reference to the metrics instance. Ps. don't add this field dynamically, that's probably impossible. But with this you can simply add another row.
So if you reference an instance of metrics. And then change that. all daily_data that references that will be "changed" since they're still referenceing the same instance.
If you need to reference more the one metrics use ManyToMany
I strongly recommend that you add a Foreign Key for DailyData to Metrics model.
class Metrics(model.Model):
...
related_day = models.ForeignKey(DailyData, on_delete=models.CASCADE, related_name="metrics", related_query_name="metrics", null=True)
Now you also need to add a signal to trigger after creating a metric to connect that metric to its related data.
#receiver(post_save, sender=Metrics)
def add_to_daily_data(sender, instance, created, **kwargs):
if created:
# Put your logic to add a specific metric to a daily data
Also, this way you can access all metrics data related to specific DailyData objects hassle-free.
daily_data.metrics.all()
models.py:
class Server(models.Model):
name = models.CharField(max_length=100, unique=True)
last_interaction = models.OneToOneField('Interaction',
on_delete=models.CASCADE,
related_name='server')
class Interaction(models.Model):
client = models.CharField(max_length=100)
time = models.DateTimeField()
server = models.ForeignKey(Server,
on_delete=models.CASCADE,
related_name="interactions")
How do I save instances? (When I want to save one, the other isn't saved yet, so it can't be saved)
Notes:
I been there. The accepted answer doesn't provide a solution to the problem. Indeed at runtime there will be new servers and clients.
I know this schema makes little sense, but I still want to find a way to work with this.
I know the on_delete=cascade in Server is dangerous. I plan to fix it once I solve this problem.
Fundamentally, if you create a circular foreign key relationship, at least one of them must be nullable. For example:
class Interaction(models.Model):
client = models.CharField(max_length=100)
time = models.DateTimeField()
server = models.ForeignKey(Server,
on_delete=models.CASCADE,
related_name="interactions",
null=True
)
You'll be able to create Interaction objects without having the corresponding Server instance ready.
I have user profiles that are each assigned a manager. I thought using recursion would be a good way to query every employee at every level under a particular manager. The goal is, if the CEO were to sign in, he should be able to query everyone at the company - but If I sign on I can only see people in my immediate team and the people below them, etc. until you get to the low level employees.
However when I run the following:
def team_training_list(request):
# pulls all training documents from training document model
user = request.user
manager_direct_team = Profile.objects.filter(manager=user)
query = Profile.objects.filter(first_name='fake')
trickle_team = manager_loop(manager_direct_team, query)
# manager_trickle_team = manager_direct_team | trickle_team
print(trickle_team)
def manager_loop(list, query):
for member in list:
user_instance = User.objects.get(username=member)
has_team = Profile.objects.filter(manager=user_instance)
if has_team:
query = query | has_team
manager_loop(has_team, query)
else:
continue
return query
It only returns the last query that was run instead of the compiled queryset that I am trying to grow. I've tried placing 'return' before 'manager_loop(has_team, query) in order save the values but it also kills the loop at the first non-manager employee instead of continuing to the next employee.
I'm new to django so if there is an better way than recursion to pull the information that I need, I'd appreciate suggestions on that too.
EDIT:
As requested, here is the profile model.
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
first_name = models.CharField(max_length=30, blank=False)
last_name = models.CharField(max_length=30, blank=False)
email = models.EmailField( blank=True, help_text='Optional',)
receive_email_notifications = models.BooleanField(default=False)
mobile_number = models.CharField(
max_length=15,
blank=True,
help_text='Optional'
)
carrier_options = (
(None, ''),
('#txt.att.net', 'AT&T'),
('#messaging.sprintpcs.com', 'Sprint'),
('#tmomail.net', 'T-Mobile'),
('#vtext.com', 'Verizon'),
)
mobile_carrier = models.CharField(max_length=25, choices=carrier_options, blank=True,
help_text='Optional')
receive_sms_notifications = models.BooleanField(default=False)
job_title = models.ForeignKey(JobTitle, unique=False, null=True)
manager = models.ForeignKey(User, unique=False, blank=True, related_name='+', null=True)
Ok, so it's a hierarchical model.
The problem with your current approach is this line:
query = query | has_team
This reassigns the local name query to a new queryset, but does not reassign the name in the caller. (Well, that's what I think it's trying to do - I am a little rusty but I don't think you can just | together querysets like that.) You'd also need something like:
query = manager_loop(has_team, query)
to propagate the changes via the returned object.
That said, while Django doesn't have built-in support for recursive queries, there are some third party packages that do. Old answers eg (Django self-recursive foreignkey filter query for all childs and Creating efficient database queries for hierarchical models (django)) recommend django-mptt. Your tag mentions postgres, so this post might be relevant:
https://two-wrongs.com/fast-sql-for-inheritance-in-a-django-hierarchy
If you don't use a third-party approach, it should be possible to clean up the evolution of the queryset - cast it to a set and use update or something, since you're accumulating profiles. But the key error is not using the returned modified object.
I know that filtering by property is not possible with Django, as filtering is done at database level and properties live in Python code. However, I have the following scenario:
In one hand, I have the model RegisteredUser on the other hand Subscription. A user can have multiple subscriptions, a subscription is from one user and a user has one or none active subscriptions.
To implement this, I have a foreign key from Subscription to RegisteredUser and a property subscription at RegisteredUser that points to the active one (latest created subscription for that user) or none if he hasn't any subscriptions.
Which would be the most efficent way to filter users that have subscription "platinum", "gold", "silver"...? I could do a "fetch all subscriptions" and then iterate over them to check each one for a match. But it would be really expensive and if I have to do the same process for each kind of subscription type, then cost would be s * u (where s is the number of different subscriptions and u is the number of users).
Any help will be appreciated. Thanks in advance!
UPDATE:
When I first explained the problem, I didn't include all the models related to
simplify a litte. But as you are asking me for the models and some of you haven't understood me
(perhaps I wasn't clear enough) here you have the code.
I've simplified the models and stripped out code that is not important now.
What do I have here? A RegisteredUser can have many subscriptions (because he may change it
as many times as he wants), and a subscription is from just one user. The user has only
one current subscription, which is the latest one and is returned by the property
subscription. Subscription is attached with Membership and this is the model whose
slug can be: platinum, gold, silver, etc.
What do I need? I need to lookup Content whose author has a specific kind of membership.
If the property approach worked, I'd have done it like this:
Content.objects.filter(author__id__in=RegisteredUser.objects.filter(
subscription__membership__slug="gold"))
But I can't do this because properties can't be used when filtering!
I thought that I could solve the problem converting the "virtual" relation created by
the property into a real ForeignKey, but this may cause side effects, as I should update it manually each time a user changes its subscription and now it's automatic! Any better ideas?
Thanks so much!
class RegisteredUser(AbstractUser):
birthdate = models.DateField(_("Birthdate"), blank=True, null=True)
phone_number = models.CharField(_("Phone number"), max_length=9, blank=True, default="")
#property
def subscription(self):
try:
return self.subscriptions_set.filter(active=True).order_by("-date_joined",
"-created")[0]
except IndexError:
return None
class Subscription(models.Model):
date_joined = models.DateField(_("Date joined"), default=timezone.now)
date_canceled = models.DateField(_("Date canceled"), blank=True, null=True)
subscriber = models.ForeignKey(AUTH_USER_MODEL, verbose_name=_("Subscriber"),
related_name="subscriptions_set")
membership = models.ForeignKey(Membership, verbose_name=_("Membership"),
related_name="subscriptions_set")
created = models.DateTimeField(_("Created"), auto_now_add=True)
last_updated = models.DateTimeField(_("Last updated"), auto_now=True)
active = models.BooleanField(_("Active"), default=True)
class Membership(models.Model):
name = models.CharField(_("Name"), max_length=15)
slug = models.SlugField(_("Slug"), max_length=15, unique=True)
price = models.DecimalField(_("Price"), max_digits=6, decimal_places=2)
recurring = models.BooleanField(_("Recurring"))
duration = models.PositiveSmallIntegerField(_("Duration months"))
class Content(models.Model):
author = models.ForeignKey(AUTH_USER_MODEL, verbose_name=_("Author"),
related_name="contents_set")
title = models.CharField(_("Title"), max_length=50)
slug = models.SlugField(_("Slug"), max_length=70, unique=True)
content = RichTextField(_("Content"))
date = models.DateField(_("Date"), default=timezone.now)
published = models.BooleanField(_("Published"))
Finally, to solve the problem I replaced the subscription property by a real foreign key and added a signal to attach the RegisteredUser with the created subscription.
Foreign key:
subscription = models.ForeignKey(Subscription, verbose_name=_("Subscription"),
related_name='subscriber_set', blank=True, null=True)
Signal:
#receiver(post_save, sender=Subscription)
def signal_subscription_post_save(sender, instance, created, **kwargs):
if created:
instance.subscriber.subscription = instance
instance.subscriber.save()
I think you model are something like:
KIND = (("p", "platinum"), ("g","gold"), ("s","silver"),)
class RegisteredUser(models.Model):
# Fields....
class Subscription(models.Model):
kind = models.CharField(choices=KIND, max_len=2)
user = models.ForeignKey(RegisteredUser, related_name="subscriptions")
Now, you can do something like that:
gold_users = RegisteredUser.objects.filter(subscriptions_kind="g")
silver_users = RegisteredUser.objects.filter(subscriptions_kind="s")
platinum_users = RegisteredUser.objects.filter(subscriptions_kind="p")
Adapt it to your models
Hope helps
EDIT
Now, With your models, I think you want something like:
content_of_golden_users = Content.objects.filter(author__subscriptions_set__membership__slug="golden")
content_of_silver_users = Content.objects.filter(author__subscriptions_set__membership__slug="silver")
content_of_platinum_users = Content.objects.filter(author__subscriptions_set__membership__slug="platinum")