Flask sqlalchemy, how to avoid circular dependency in this case? - database

I'v been banging my head over this for 2 days straight and i still can understand/find a way to do this and it looks super simple but im obviously overlooking something (also im fairly new to DBs :) ).
I want to have Owner and Pet model.
Pets have 'owner ids' as foreign keys, and Owners have 'pets' as relationship, so far so good.
But now i also want Owners to have one 'pet id' written as 'favorite pet'.
Having foreign keys in both models (each others keys) started to make bunch of different problems (different depending on how i try to solve it, but either circular dependency or some multipath error)
I also noticed that if i avoid having 'favourite_pet_id'-foreign key in Owner model, keeping only favourite_pet-relationship, then i dont have this written anywhere in DB (at least not visible), it exists only as 'relationship' ?
What would be correct way of doing this ?
Thanks in advance !
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite://'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
class Owner(db.Model):
id = db.Column(db.Integer, primary_key=True)
pets = db.relationship('Pet', foreign_keys='Pet.owner_id')
favourite_id = db.Column(db.Integer, db.ForeignKey('pet.id'))
favourite = db.relationship('Pet', uselist=False, foreign_keys='Owner.favourite_id')
class Pet(db.Model):
id =db.Column(db.Integer, primary_key=True)
owner_id = db.Column(db.Integer, db.ForeignKey('owner.id'))
owner = db.relationship('Owner', uselist=False, back_populates='pets', foreign_keys='Pet.owner_id')
o = Owner() # one owner
p1 = Pet() # pet 1
p2 = Pet() # pet 2
p1.owner=o # setting owner for pet1
p2.owner=o # setting owner for pet2
o.favourite=p2 # setting pet2 to be favourite
#db.session.add(o)
#db.session.add(p1)
#db.session.add(p2)
#db.session.commit()
print (p1.owner) # owner
print (p2.owner) # owner
print (p1) # pet 1
print (p2) # pet 2
print (o.pets) # owners pets
print (o.favourite) # favourite pet

Here's a working version of your code below. The key is being more explicit about the relationship (e.g. join condition) as there are multiple relationships between the models/tables.
Note: While not part of your question/request, I also reformatted a bit for PEP8 conformity and readability. The later is a good practice to adopt early on as model files typically grow quickly and can become very difficult to read, digest, and debug.
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
class Owner(db.Model):
id = db.Column(
db.Integer,
primary_key=True,
)
favourite_pet_id = db.Column(
db.Integer,
db.ForeignKey('pet.id'),
nullable=True,
)
favourite_pet = db.relationship(
'Pet',
uselist=False,
foreign_keys=[favourite_pet_id],
primaryjoin='Pet.owner_id == Owner.favourite_pet_id',
)
class Pet(db.Model):
id = db.Column(
db.Integer,
primary_key=True,
)
owner_id = db.Column(
db.Integer,
db.ForeignKey('owner.id'),
)
owner = db.relationship(
'Owner',
uselist=False,
foreign_keys=[owner_id],
primaryjoin='Pet.owner_id == Owner.id',
backref=db.backref(
'pets',
uselist=True,
),
)
db.create_all()
o = Owner() # one owner
p1 = Pet() # pet 1
p2 = Pet() # pet 2
p1.owner = o # setting owner for pet1
p2.owner = o # setting owner for pet2
o.favourite_pet = p2 # setting pet2 to be favourite
print(p1.owner) # owner
print(p2.owner) # owner
print(p1) # pet 1
print(p2) # pet 2
print(o.pets) # owners pets
print(o.favourite) # favourite pet

Related

display the extensions of a church in relation to the location or the address entered by the user with django

someone can help me?
I am creating a website for my church with django with an extension menu that will represent all church extensions worldwide. my problem is that i want a system that will show the user churches near him or in his area. put a text box to ask for his current address and show him all the parishes near him and their locations. except that the extensions are classified as follows: country > Province > city > Commune > district > parish
at my first reflection I tried to create the following models
from django.db import models
# Create your models here.
class Pays(models.Model):
Ctn = (
('Africa', 'Africa'),
('America','America'),
('Asia','Asia'),
('Europe','Europe'),
)
STATUS = (
('True', 'True'),
('False','False'),
)
name=models.CharField(max_length=100)
continent=models.CharField(max_length=50, default="Afrique")
slug=models.SlugField()
date_created=models.DateTimeField(auto_now_add=True)
status=models.CharField( choices=STATUS, max_length=100)
def __str__(self):
return self.name
class Church(models.Model):
STATUT = (
('En Service', 'En Service'),
('Fermée','Fermée'),
)
CAT = (
('Siège', 'Siège'),
('Eglise centrale', 'Eglise centrale'),
('Eglise locale','Eglise locale'),
)
name=models.CharField(max_length=100)
address=models.CharField(max_length=255)
Town=models.CharField(max_length=150)
Contry=models.ForeignKey(Pays,on_delete=models.CASCADE )
description=models.CharField(max_length=255)
effectif=models.IntegerField()
slug=models.SlugField()
date_created=models.DateTimeField(auto_now_add=True)
statut=models.CharField(choices=STATUT,max_length=100)
category=models.CharField(choices=CAT,max_length=100)
def __str__(self):
return self.name
but the problem is when I want to display a parish which is in a district of a municipality of a city of a province of a country of a determined continent example: continent= africa, country=DRC (democratic republic of congo), province= Kantanga, city=Lubumbashi, commune=rwashi and parish=bel'air 1.
I can't manage to build a django query that will return the parish to me in this order that I just showed above

how to use initiate function for a cart in Django?

I'm learning Django and I'm trying to make a Cart, which the customer can get and item and add it in his/her order row and then the order will be submitted. so my teacher said use def initiate(customer), and I don't understand how to use it. Can someone please explain it to me? Thank you.
here is the code I'm working on it:
User = get_user_model()
class Customer(models.Model):
user = models.OneToOneField(User, on_delete=Product, related_name="User")
phone = models.CharField(max_length=20)
address = models.TextField()
balance = models.IntegerField(default=20000)
def deposit(self, amount):
self.balance += amount
self.save()
def spend(self, amount):
if amount > self.balance:
raise ValueError
self.balance -= amount
self.save()
class OrderRow(models.Model):
product = models.ManyToManyField(Product)
order = models.ForeignKey('Order', on_delete=models.CASCADE)
amount = models.IntegerField()
class Order(models.Model):
# Status values. DO NOT EDIT
STATUS_SHOPPING = 1
STATUS_SUBMITTED = 2
STATUS_CANCELED = 3
STATUS_SENT = 4
customer = models.ForeignKey('Customer', on_delete=models.SET_NULL)
order_time = models.DateTimeField(auto_now=True)
total_price = Sum(F('amount') * F('product__price'))
status = models.IntegerField(choices=status_choices)
#staticmethod
def initiate(customer):
Order.initiate(User)
def add_product(self, product, amount):
Order.status = 1
OrderRow.product = Product.objects.get(id=product.id)
print(product.id)
if OrderRow.objects.filter(product=product).exists():
preexisting_order = OrderRow.objects.get(product=product, order=self)
preexisting_order.amount += 1
preexisting_order.save()
else:
new_order = OrderRow.objects.create(
product=product,
cart=self,
amount=1,
)
new_order.save()
You are probably supposed to create a new Order associated with this customer. Something along the following lines:
#classmethod
def initiate(cls, customer):
return cls.objects.create(customer=customer, status=cls.STATUS_SHOPPING)
There are some other issues with your code. You cannot use SET_NULL if the fk is not nullable:
customer = models.ForeignKey('Customer', on_delete=models.SET_NULL, null=true)
There should not be multiple products per row:
class OrderRow(models.Model):
product = models.ForeignKey(Product) # not many2many!
# ...
Also, your add_product needs quite some fixing:
def add_product(self, product, amount):
self.status = self.STATUS_SHOPPING # the instance is self + use your descriptive variables
print(product.id)
# filter only rows in the current order!
if self.orderrow_set.filter(product=product).exists():
# fix naming: this is a row, not an order
preexisting_order_row = self.orderrow_set.get(product=product)
preexisting_order_row.amount += amount # why +1, you are adding amount
preexisting_order_row.save()
else:
new_order_row = OrderRow.objects.create(
product=product,
order=self,
amount=amount,
) # create saves already

How does field renaming works on django migrations?

Django migration can detect if a field was renamed and ask you about it (instead of the old fashion delete/create)
Even if multiple fields are changed it seems to find the corresponding match. For example:
Before:
class DirectoryMirror(models.Model):
directory_origin = models.ForeignKey(TapeDirectory)
machine_target = models.ForeignKey(GenericMachine)
directory_target = models.CharField(max_length=255, blank=False)
After (changing field names):
class DirectoryMirror(models.Model):
source_directory = models.ForeignKey(TapeDirectory)
target_machine = models.ForeignKey(GenericMachine)
target_directory = models.CharField(max_length=255, blank=False)
Generating migration:
$ ./manage.py makemigrations
Did you rename directorymirror.directory_origin to directorymirror.source_directory (a ForeignKey)? [y/N] y
Did you rename directorymirror.directory_target to directorymirror.target_directory (a CharField)? [y/N] y
Did you rename directorymirror.machine_target to directorymirror.target_machine (a ForeignKey)? [y/N] y
How does it manage to detect the renaming and find the correct match?
Here it is the algorithm https://github.com/django/django/blob/bc77eb6d0858652e197c08c299efaeb06c51efee/django/db/migrations/autodetector.py#L757
Copying it here
def generate_renamed_fields(self):
"""
Works out renamed fields
"""
self.renamed_fields = {}
for app_label, model_name, field_name in sorted(self.new_field_keys - self.old_field_keys):
old_model_name = self.renamed_models.get((app_label, model_name), model_name)
old_model_state = self.from_state.models[app_label, old_model_name]
field = self.new_apps.get_model(app_label, model_name)._meta.get_field(field_name)
# Scan to see if this is actually a rename!
field_dec = self.deep_deconstruct(field)
for rem_app_label, rem_model_name, rem_field_name in sorted(self.old_field_keys - self.new_field_keys):
if rem_app_label == app_label and rem_model_name == model_name:
old_field_dec = self.deep_deconstruct(old_model_state.get_field_by_name(rem_field_name))
if field.remote_field and field.remote_field.model and 'to' in old_field_dec[2]:
old_rel_to = old_field_dec[2]['to']
if old_rel_to in self.renamed_models_rel:
old_field_dec[2]['to'] = self.renamed_models_rel[old_rel_to]
if old_field_dec == field_dec:
if self.questioner.ask_rename(model_name, rem_field_name, field_name, field):
self.add_operation(
app_label,
operations.RenameField(
model_name=model_name,
old_name=rem_field_name,
new_name=field_name,
)
)
self.old_field_keys.remove((rem_app_label, rem_model_name, rem_field_name))
self.old_field_keys.add((app_label, model_name, field_name))
self.renamed_fields[app_label, model_name, field_name] = rem_field_name
break

ndb unique key in range

I'm using google app engine and need to have the keys of an entity between 1000 and 2^31. I'm considering 2 ways of doing this:
1) keep a counter of the created keys as detailed here https://cloud.google.com/appengine/articles/sharding_counters. But this requires several datastore read/writes for every key and I'm not sure it is guaranteed to be consistent.
2) generate a random int in my range and check if that key is already in the database. To make it cheap, i'd like a keys_only query, but i can't find a way to do this except saving the key also as a separate field:
MyEntity.query(MyEntity.key_field==new_random_number).fetch(keys_only=True)
Is there a better way to achieve this?
How many writes per second are you expecting in production? Both of your proposals are good, but for our application I decided to go with a sharded counter approach. You can also set the id of an entity before you put it to avoid the query altogether:
MyModel(id="foo")
then you can look it up:
MyModel.get_by_id("foo")
Id doesn't have to be a string, it can be a number also:
MyModel(id=123)
If you decide to go with the sharded counter, here's our production-level code which is darn close what you read in that article ;o) Memcache adds the level of consistency we needed to be able to get the right count.
class GeneralShardedCounterConfig(ndb.Model):
SHARD_KEY_TEMPLATE = 'gen-count-{}-{:d}'
num_shards = ndb.IntegerProperty(default=200)
#classmethod
def all_keys(cls, name):
config = cls.get_or_insert(name)
shard_key_strings = [GeneralShardedCounterConfig.SHARD_KEY_TEMPLATE.format(name, index)
for index in range(config.num_shards)]
return [ndb.Key(GeneralShardedCounter, shard_key_string)
for shard_key_string in shard_key_strings]
class GeneralShardedCounter(BaseModel):
count = ndb.IntegerProperty(default=0)
#classmethod
def get_count(cls, name):
total = memcache.get(name)
if total is None:
total = 0
all_keys = GeneralShardedCounterConfig.all_keys(name)
for counter in ndb.get_multi(all_keys):
if counter is not None:
total += counter.count
memcache.set(name, total, constants.SHORT_MEMCACHE_TTL)
return total
#classmethod
#ndb.transactional(retries=5)
def increase_shards(cls, name, num_shards):
config = GeneralShardedCounterConfig.get_or_insert(name)
if config.num_shards < num_shards:
config.num_shards = num_shards
config.put()
#classmethod
#ndb.transactional(xg=True)
def _increment(cls, name, num_shards):
index = random.randint(0, num_shards - 1)
shard_key_string = GeneralShardedCounterConfig.SHARD_KEY_TEMPLATE.format(name, index)
counter = cls.get_by_id(shard_key_string)
if counter is None:
counter = cls(id=shard_key_string)
counter.count += 1
counter.put()
# Memcache increment does nothing if the name is not a key in memcache
memcache.incr(name)
#classmethod
def increment(cls, name):
config = GeneralShardedCounterConfig.get_or_insert(name)
cls._increment(name, config.num_shards)
#classmethod
def _add(cls, name, value, num_shards):
index = random.randint(0, num_shards - 1)
shard_key_string = GeneralShardedCounterConfig.SHARD_KEY_TEMPLATE.format(name, index)
counter = cls.get_by_id(shard_key_string)
if counter is None:
counter = cls(id=shard_key_string)
counter.count += value
counter.put()
# Memcache increment does nothing if the name is not a key in memcache
memcache.incr(name, value)
#classmethod
def add(cls, name, value):
config = GeneralShardedCounterConfig.get_or_insert(name)
cls._add(name, value, config.num_shards)
Example of get_or_insert. Insert 7 unique keys
import webapp2
from google.appengine.ext import ndb
from datetime import datetime
import random
import logging
class Examples(ndb.Model):
data = ndb.StringProperty()
modified = ndb.DateTimeProperty(auto_now=True)
created = ndb.DateTimeProperty() # NOT auto_now_add HERE !!
class MainHandler(webapp2.RequestHandler):
def get(self):
count = 0
while count < 7:
random_key = str(random.randrange(1, 9))
dt_created = datetime.now()
example = Examples.get_or_insert(random_key, created=dt_created, data='some data for ' + random_key)
if example.created != dt_created:
logging.warning('Random key %s not unique' % random_key)
continue
count += 1
self.response.write('Keys inserted')
app = webapp2.WSGIApplication([
('/', MainHandler)
], debug=True)

Double Relationship in SQLalchemy

I got following problem in sqlalchemy. I made three different tables in sqlite, the first has no realtion, the second has a relation to the first and the third a relation to the second. So when I want to insert things in the first and the second table everything works fine. When I want to insert datas in the third table I'm getting troubles when I'm going to do it like it's discribed in the tutorial. Here is my code for the three tables:
First table:
# Save_Data_Type.py
from sqlalchemy import ForeignKey
from sqlalchemy import Column, Float, Integer, String
from base import Base
from sqlalchemy.orm import relationship, backref
########################################################################
class Save_Data_Type(Base):
__tablename__ = "save_datas_type"
save_datas_type_id = Column(Integer, primary_key=True)
type_memory = Column(String)
comment = Column(String)
dummy1 = Column(String)
dummy2 = Column(String)
#----------------------------------------------------------------------
def __init__(self, type_memory, comment, dummy1, dummy2):
""""""
self.type_memory = type_memory
self.comment = comment
self.dummy1 = dummy1
self.dummy2 = dummy2
Second Table
# Save_Data.py
from sqlalchemy import ForeignKey
from sqlalchemy import Column, Float, Integer, String
from base import Base
from sqlalchemy.orm import relationship, backref
########################################################################
class Save_Data(Base):
""""""
__tablename__ = "save_datas"
save_datas_id = Column(Integer, primary_key=True)
save_datas_type_id = Column(Integer,ForeignKey("save_datas_type.save_datas_type_id"))
save_datas_type = relationship("Save_Data_Type", backref=backref("save_datas", order_by=save_datas_id))
value = Column(Float)
comment = Column(String)
dummy1 = Column(String)
#----------------------------------------------------------------------
def __init__(self, value, comment, dummy1):
""""""
self.value = value
self.comment = comment
self.dummy1 = dummy1
Third Table
# Station.py
from sqlalchemy import ForeignKey
from sqlalchemy import Column, Integer, Boolean, String
from base import Base
from sqlalchemy.orm import relationship, backref
########################################################################
class Station(Base):
""""""
__tablename__ = "stations"
stations_id = Column(Integer, primary_key=True)
name = Column(String)
password = Column(String)
save_datas_id = Column(Integer,ForeignKey("save_datas.save_datas_id"))
save_datas = relationship("Save_Data", backref=backref("stations", order_by=stations_id))
dummy1 = Column(String)
dummy2 = Column(String)
dummy3 = Column(String)
#----------------------------------------------------------------------
def __init__(self, name, password, dummy1, dummy2, dummy3):
""""""
self.name = name
self.password = password
self.dummy1 = dummy1
self.dummy2 = dummy2
self.dummy3 = dummy3
base.py
# base.py
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
So if I'm going to insert the datas like that:
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
import base
from Save_Data_Type import Save_Data_Type
from Save_Data import Save_Data
from Station import Station
engine = create_engine('sqlite:///Database.db', echo=True)
base.Base.metadata.create_all(engine, checkfirst=True)
# create a Session
Session = sessionmaker(bind=engine)
session = Session()
jack = Save_Data_Type("Ring Memory",'In seconds',None,None)
jack.save_datas = [Save_Data(1980,'Sometimes more, sometimes less',None)]
jack.save_datas.stations = [Station('name1','123456',None,None,None)]
session.add(jack)
session.commit()
Nothing is writing in the the third table of the database. How is the usual way to build this relationship?
Thanks in advance,
Johannes
Apart from some typos in the code you posted (see comment from zzzeek), it looks like the problem will be solved by replacing the code:
jack.save_datas.stations = [Station('name1','123456',None,None,None)]
with the one below:
jack.save_datas[0].stations = [Station('name1','123456',None,None,None)]
The reason is the following: in your case you assign/create a stations attribute to a class member. Not only this does not do the right job, it might actualy harm your ORM model.
In the second case you correctly assign the Station instance to the first save_datas.
The more clear code would look like this:
jack = Save_Data_Type("Ring Memory",'In seconds',None,None)
save_data1 = Save_Data(1980,'Sometimes more, sometimes less',None)
jack.save_datas.append(save_data1)
# or: jack.save_datas = [save_data1]
# or (my favourite): save_data1.Save_Data_Type = jack
save_data1.stations = [Station('name1','123456',None,None,None)]
# or... similar to the relationship above

Resources