I'm new here, working with Web Apps & SEO. StackOverflow has been a great resource in learning my way around Python & Django so a big thank you to the community. Now for my Question!
I have a couple of Django models:
class Subscription(models.Model):
hotel = models.ForeignKey("Hotel", related_name="subscriptions")
tier = models.ForeignKey("Tier")
enquiry_count = models.PositiveIntegerField(default=0)
start_date = models.DateField(null=True, blank=True)
end_date = models.DateField(null=True, blank=True)
and:
class Tier(models.Model):
name = models.CharField(max_length=32)
enquiry_limit = models.PositiveIntegerField(default=0)
I also have a Hotel Model that i'll show here in a very simlified form:
class Hotel(models.Model):
name = models.CharField("Hotel Name", max_length=128)
address = models.TextField("Address", blank=True)
town = models.CharField(max_length=64)
star = models.PositiveIntegerField(default=0, null=True, blank=True)
Each Hotel needs to have subscription to appear in my search results. Each Subscription has a Tier with a certain enquiry_limit.
A subscription runs out when it either reaches the end_date OR its enquiry_count maxes out. i.e. Reaches the enquiry_limit of its tier.
I found a straightforward way of doing this with F Objects and an exclude which works nicely on my dev machine:
self.premium_hotels = Hotel.objects.select_related().exclude(
Q(subscriptions__end_date__lte=datetime.date.today()) | Q(subscriptions__enquiry_count__gte=F('subscriptions__tier__enquiry_limit')))
This will not work on the live version of the site however as it is running Django 1.0. Any tips on how to achieve this query without F objects?
I realize that the obvious solution is to upgrade but we need this rolled out right away and I'll need time to prepare & test before we move to Django 1.3
Thanks in advance!
Without F objects you will not be able to do the query in a single step, your own option is to run the query without looking at the enquiry_limit and the post process the list to remove those hotels which have exceeded it.
Something like the code below should work.
hotels = Hotel.objects.select_related().exclude(
Q(subscriptions__end_date__lte=datetime.date.today()))
self.premium_hotels = []
for h in hotels:
for sub in h.subscriptions.filter(start_date__lte=datetime.now(), end_date__gte=datetime.now()):
if sub.enquiry_count < sub.tier.enquiry_limit:
self.premium_hotels.append(h)
break
Use extra on the queryset to insert custom SQL (see where / tables):
http://docs.djangoproject.com/en/dev/ref/models/querysets/#extra
e.g. (roughly - you'll need to match tables name etc in SQL)
self.premium_hotels = Hotel.objects.select_related().exclude(
Q(subscriptions__end_date__lte=datetime.date.today())).extra(tables=["myapp_tier"], where=['myapp_subscriptions.enquiry_count
Best bet it to run your query locally - look at what SQL it generates, that use that to figure out what to put in your call to extra
Related
I'm creating a WebApp with Django as a personal project. This app tracks hiking challenges and gets the weather from a weather database with an API. My problem is currently with creating a database structure that will be a good design and expandable in the future.
Currently, I have two Tables.
(Django Models)
class Mountain(models.Model):
mnt_name = models.CharField(max_length=100)
latitude = models.FloatField()
longitude = models.FloatField()
elevation = models.IntegerField(default=0)
distance = models.IntegerField(default=0)
users_completed = models.ManyToManyField(User)
# Commented from code to prevent issues
# date_done = models.DateTimeField(blank=True, null=True, default=None)
def __str__(self):
return self.mnt_name
class Challenge(models.Model):
challenge_name = models.CharField(max_length=101)
mountains = models.ManyToManyField(Mountain)
def __str__(self):
return self.challenge_name
I have some of the functionality I was looking for. I can loop through the list of users_completed and compare them against the currently logged in user and if the user is in the list update the completed status. But That isn't a good solution because I cant have user-specific dates completed among other reasons.
Intuitively I thought the best way to go about fixing this issue is to add a copy for each challenge to each user profile so the user can have user-specific data such as the completion status and the date completed. But I'm not sure how I would go about doing that or If it's even possible.
Any suggestions on how I should rearrange my models so that I can have user-specific data such as if the mountain has been completed, and date of completion, etc.
All help is greatly appreciated. Thank you.
For this, you can use a third table to map the user to the mountain\challenge.
Achievement:
AchievementID
UserID
MountainID -- set when mountain completed
ChallengeID -- set when full challenge completed
AchievementDate
An entry is created when a user completes a mountain or a full challenge.
I would probably use a double through implementation.
from django.db import models
from django.contrib.auth.models import User
class Mountain(models.Model):
mnt_name = models.CharField(max_length=100)
latitude = models.FloatField()
longitude = models.FloatField()
elevation = models.IntegerField(default=0)
distance = models.IntegerField(default=0)
class MountainChallenge(models.Model):
"""This allows you to use the same mountain in multiple challenges."""
mountain = models.ForeignKey(Mountain, on_delete=models.CASCADE)
challenge = models.ForeignKey("Challenge", on_delete=models.CASCADE)
users_completed = models.ManyToManyField(User, through="UserMountain")
class UserMountain(models.Model):
"""This associates a user to a MoutainChallenge while storing other data."""
user = models.ForeignKey(User, on_delete=models.CASCADE)
mountain_challenge = models.ForeignKey(MountainChallenge, on_delete=models.CASCADE)
date = models.DateTimeField()
class Challenge(models.Model):
challenge_name = models.CharField(max_length=101)
mountains = models.ManyToManyField(Mountain, through="MountainChallenge")
Note I haven't tested this code.
You store Moutain objects independently from challenges.
You have a MoutainChallenge which represents a Mountain as part of a Challenge
It can't be a simple ManyToMany because you want to associate a list of users who have completed the moutain
This is done with UserMountain which can hold a date or other things related to the achievement
On the business side of your app, when a UserMountain is achieved you should check if the current user has achieved all the MoutainChallenges of the current Challenge
Hopefully this makes sense without over-engineering.
I think it leaves room for expansion in the future.
How can i get the equivalent of this MySQL script in Django views? The depart_city, arrive_city, and travel_date will be inputed by the user.
Here is my Models
class Driver(models.Model):
first_name = models.CharField(max_length=30, null=True, blank=False)
last_name = models.CharField(max_length=30, null=True, blank=False)
class Schedule(models.Model):
depart_time = models.TimeField()
arrive_time = models.TimeField()
class TravelDate(models.Model):
start_date = models.DateField(null = True)
interval = models.IntegerField(null = True)
class Route(models.Model):
depart_city = models.CharField(max_length=50, null=True, blank=False)
arrive_city = models.CharField(max_length=50, null=True, blank=False)
driver = models.ForeignKey(Driver)
schedule = models.ForeignKey(Schedule)
traveldate = models.ForeignKey(TravelDate)
Here is my MySQL script. This works when i run it on MySQL workbench but I'm not sure how to translate this to Django Query
SELECT busapp_route.depart_city, busapp_route.arrive_city, busapp_driver.first_name, busapp_schedule.depart_time
FROM (((busapp_route INNER JOIN busapp_driver ON busapp_route.driver_id = busapp_driver.id)
INNER JOIN busapp_schedule ON busapp_route.schedule_id = busapp_schedule.id)
INNER JOIN busapp_traveldate ON busapp_route.traveldate_id = busapp_traveldate.id)
WHERE busapp_route.depart_city='Tropoje' AND busapp_route.arrive_city='Tirane'
AND (DATEDIFF('2017-11-26', busapp_traveldate.start_date) % busapp_traveldate.interval = 0);
Django doesn't support MySQL DATEDIFF natively. As a workaround, you could use something like this:
from django.db.models.expressions import RawSQL
routes = Route.objects\
.values('depart_city', 'arrive_city', 'driver__first_name', 'schedule__depart_time', 'traveldate__start_date')\
.annotate(datediff_mod=RawSQL("DATEDIFF(%s, busapp_traveldate.start_date) MOD busapp_traveldate.interval", ('2017-11-26', )))\
.filter(depart_city='Tropoje', arrive_city='Tirane', datediff_mod = 0)
I don't use MySQL so I couldn't test this, but I'm pretty sure it should work or maybe at least give you an idea how to implement it.
I don't think that it's a good idea, trying to solve this from the SQL side.
And what you're building there, is not trivial as there are a lot of things to keep in mind.
Your current Model setup lags some points:
What if a user searches for a travel in next year? With your model you'll have to create entries for all possible dates in the future, that's nearly unusable.
Think also about separating times and dates in different models, as they usually should be kept together, cause a ride from A to B at date x and time y is one object.
It might help to have a look at django-scheduler, they solved some troubles with the use of Events and Occurences. Have a look at their pretty good docs.
I'm starting my first Django web app [after completing the Django official tutorial and Django Girls tutorial].. I'm having some trouble wrapping my head around setting up models. I have something now that works, but I feel that it is quite inefficient and I rather learn the correct way before going forward..
Starting out - When learning a new programming language, a lot of people (on the internet) advise to pick a topic/hobby you enjoy and make a project out of it. I enjoy sports a lot and wanted to recreate a virtual scoreboard, similar to ESPN or any other sports web site.
My models.py currently looks like this -
class Game(models.Model):
# start_time = models.DateTimeField()
today = models.DateField(default=date.today)
game_id = models.AutoField(primary_key=True)
game_outcome = models.CharField(max_length=8)
game_quarter = models.CharField(max_length=1)
game_clock = models.PositiveSmallIntegerField()
away_team_id = models.CharField(max_length=3)
away_team_class = models.CharField(max_length=3)
away_team_city = models.CharField(max_length=13)
away_team_name = models.CharField(max_length=12)
away_team_score = models.PositiveSmallIntegerField()
home_team_id = models.CharField(max_length=3)
home_team_class = models.CharField(max_length=3)
home_team_city = models.CharField(max_length=13)
home_team_name = models.CharField(max_length=12)
home_team_score = models.PositiveSmallIntegerField()
but I feel like this is way too many variables and not the correct way to go about it. I have also tried something like this...
class Day(models.Model):
today = models.DateField(default=date.today)
class Game(models.Model):
game_id = models.AutoField(primary_key=True)
game_outcome = models.CharField(max_length=8)
game_quarter = models.CharField(max_length=1)
game_clock = models.PositiveSmallIntegerField()
away_team_id = models.CharField(max_length=3)
away_team_score = models.PositiveSmallIntegerField()
home_team_id = models.CharField(max_length=3)
home_team_score = models.PositiveSmallIntegerField()
class Team(models.Model):
team_id = models.ForeignKey('Team')
team_class = models.CharField(max_length=3)
team_city = models.CharField(max_length=13)
team_name = models.CharField(max_length=12)
Every Day has 0 to xx amount of games. Than every game has: outcome (null or 'FINAL'), current quarter ('1' or null), current clock ('00:00' or null), away team ('CHA'), away score ('99'), home team('TOR') and home score('102'). Then I would take the team id's and compare that to the class Team. Every Team has a class (for css: 'tor'), a city ('Toronto'), and full name ('Raptors').
Still it's not sticking in my head. I can get the all the information in a python shell and output to my html/css, but I feel it's inefficient. Also, every Day has it's own html page (similar to website.com/nba/scores/20150112/) and is filled with all the games from that day.
Sorry if this is too beginner to ask; I have spent a lot of time looking for answers and finally decided to post on stackoverlow (my first question ever).
Any information or advice would be greatly appreciated.
First of all you need to understand the more ForeignKeys you use the more load you put on the database. But it also depends a lot on the views.py code as well. Second of all you don't need to use team_id as a field name if you use a ForeignKey. Just mention the class name and it will automatically create an _id for the field.
Look at the following code:
class Game(models.Model):
start_time = models.DateTimeField()
today = models.DateField(default=date.today)
game_id = models.AutoField(primary_key=True) # You don't need this line, an id field is created for EVERY model class even if you don't specify it.
game_outcome = models.CharField(max_length=8)
game_quarter = models.CharField(max_length=1)
game_clock = models.PositiveSmallIntegerField()
away_team = models.ForeignKey('Team')
home_team = models.ForeignKey('Team')
class Team(models.Model):
team_class = models.CharField(max_length=3)
city = models.CharField(max_length=13)
name = models.CharField(max_length=12)
score = models.PositiveSmallIntegerField()
You can see that I have created another class by the name of Team and put four fields on that which was repeated in your original Game class. Then I have connected the Team class to the Game class with ForeignKey. If you install SQLiteBrowser and check out the database file then you will see that the away_team field is actually the away_team_id field in the database because Django takes care of that. You don't need to create id fields, just use relations like ForeignKey, ManyToManyField, etc. Off the top of my head this is the only thing that I can suggest.
Judging by the scope of this project you will not put too much pressure on the database with too many relations, so don't worry about that now. That' it. If you have any question more specific you can ask. Cheers! :)
P.S. I am not sure about your project details so use whichever relation you see fit, be it one-to-one, many-to-one or many-to-many, etc. Read the docs for more details. :)
I think the general idea of your model approach is good enough. Nevertheless, I'd encourage you to go through Django's ORM relationships documentation. It's ok if you want to use strings as your primary key, but let Django's ORM know that by using the proper model field. If you use primary and foreign keys (relationships), your dbms will create indexes over the necessary fields, resulting in more efficient queries. Let me know if this help! Good luck with your project!
UPDATE:
It would be something like this:
class Day(models.Model):
today = models.DateField(default=date.today)
class Game(models.Model):
game_outcome = models.CharField(max_length=8)
game_quarter = models.CharField(max_length=1)
game_clock = models.PositiveSmallIntegerField()
day = models.ForeignKey(Day, on_delete=models.CASCADE)
away_team = models.ForeignKey(Team, on_delete=models.CASCADE, related_name='away_team')
away_team_score = models.PositiveSmallIntegerField()
home_team = models.ForeignKey(Team, on_delete=models.CASCADE, related_name='home_team')
home_team_score = models.PositiveSmallIntegerField()
class Team(models.Model):
team_class = models.CharField(max_length=3)
team_city = models.CharField(max_length=13)
team_name = models.CharField(max_length=12)
Notice that I omitted all id fields, since django will use one automatically created, which behaves the same as AutoField. Also notice that since Game has two references to the same class (Team), it is necessary to provide the extra parameter related_name. Mor info about it in django docs about related_name.
This way, you can do things like
game = Game.objects.find(1)
away_team_name = game.away_team.team_name
You can access the associated Team class in the away_team variable. No need to call away_team_id at all.
I have merged two querysets (qs1 and qs2) which individually work fine, as follows:
qlist = [qs1, qs2]
results = list(chain(qs1, qs2))
So far, so good - the above works. But now I'm trying to order the results using the following:
qlist = [qs1, qs2]
results = sorted(chain(qs1, qs2), key=attrgetter('monthly_fee'))
The problem is that the second queryset (qs2) refers to the monthly_fee through a ForeignKey; whereas qs1 has 'monthly_fee' available. Here is qs2:
qs2 = Offer.objects.select_related('subscription')
qs2 = qs2.order_by(subscription__monthly_fee)
And the simplified models:
class Subscription(models.Model):
monthly_fee = models.IntegerField(null=False, blank=True, default=0)
name = models.CharField(max_length=120, null=True, blank=True)
class Offer(models.Model):
promotion_name = models.CharField(max_length=120, null=True, blank=True)
subscription = models.ForeignKey(Subscription)
discount = models.IntegerField(null=False, blank=True, default=0)
I've tried using .annotate() and .extra() to rename the subscription__monthly_fee in the query qs2 as follows:
qs2 = Offer.objects.select_related('subscription').annotate(monthly_fee=subscription__monthly_fee)
But then get the error
global name 'subscription__monthly_fee' is not defined
I am at the point of just hacking this by over-riding the .save() methods of my models to manually add the monthly_fee to each Offer instance whenever an object is created. But just wanted to check whether there isn't a better way ?
Thank you,
Michael
I've used an F expression to achieve this sort of renaming before. Try this:
from django.db.models import F
qs2 = Offer.objects.select_related('subscription').annotate(monthly_fee=F('subscription__monthly_fee'))
OK, I found a way to do this.
qs2 = Offer.objects.select_related('subscription').extra(select={'monthly_fee':'mobile_subscription.monthly_fee'})
where 'mobile' is the name of the Django app. I didn't realize that .extra DOES allow you to follow foreign keys but that you actually have to specify the actual database table and use SQL dot notation.
Is the above the actual correct way we are supposed to do it ? (i.e. dropping in raw SQL table names/fields)
I had been trying to use Django syntax such as .extra(select={'monthly_fee':'subscription__monthly_fee'}) which doesn't work!
I'm struggling with django querysets and GROUP BY queries, I know there are plenty of similar questions, but I really don't get it:/
I would like to be able to create a request similar to this one (SQLite):
SELECT MAX(fb_game_score.value), fb_game_fbuser.first_name, fb_game_fbuser.last_name
FROM fb_game_score
JOIN fb_game_game ON (fb_game_score.game_id = fb_game_game.id)
JOIN fb_game_fbuser ON (fb_game_game.user_id = fb_game_fbuser.id)
GROUP BY fb_game_fbuser.fb_user_id;
The query is quite simple, it lists the users scores by showing only the best score for each players.
For clarification here's the model classes:
class FBUser(AbstractUser):
fb_user_id = models.CharField(max_length=100, null=True)
oauth_token = models.CharField(max_length=1024, null=True)
expires = models.IntegerField(null=True)
highest_score = models.IntegerField(null=True)
class Game(models.Model):
identifier = models.CharField(max_length=100, db_index=True)
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='games')
class Score(models.Model):
game = models.ForeignKey(Game, related_name='scores')
value = models.IntegerField()
date = models.DateTimeField(auto_now=True)
timestamp = models.FloatField(default=0)
inter = models.BooleanField(default=False)
There's no high-level group_by in the queryset. It's used in calls to aggregate and annotate but it is not available to you.
There's a low-level API which is not documented at all. You can get an internal query description:
queryset = ... #whatever query you'd want to group by
query = queryset.query
and then you can alter the group_by member -which is a list- by adding a field which you'd want to group by:
query.group_by.append('a_field')
But:
you have to seriously know what you're doing.
there's no guarantee of stability of this API.
The current alternative for this is falling back to a raw (django.db.connection.* methods) SQL query.
Edit: I just saw this 3rd-party application which could help you with reports. I don't know if you can use in-code reports, or you have to limit yourself to in-view reports (i.e.: don't know if you can process reports in code or just have them as final results).