Turbogears2 AdminController throwing an error with a many-to-many relationship - python-2.7

I'm having an issue with turbogears admin controller throwing an error when I try to edit the User, ShoppingItem or ShoppingList items (code below). The error coming up is AttributeError: 'function' object has no attribute 'primary_key'. The Local Variables in frame always come back as the same:
mapper
<Mapper at 0x3719810; ShoppingList>
fields
['id']
self
<sprox.sa.provider.SAORMProvider instance at 0x03E537B0>
value
<bound method OrderedProperties.items of <sqlalchemy.util._collections.OrderedProperties object at 0x037199F0>>
entity
<class 'insertmealhere.model.shoppinglist.ShoppingList'>
field_name
'items'
I'm having trouble figuring out what is different between this and the other many-to-many relationships that are configured elsewhere in the code and are not throwing this error. I'm running Turbogears 2.2 on Python 2.7.8 currently on a windows 8.1 system. Any help is greatly appreciated.
list_item_table = Table("list_item_table", metadata,
Column('item_id', Integer, ForeignKey('shopping_item.id', onupdate="CASCADE", ondelete="CASCADE"), primary_key=True),
Column('list_id', Integer, ForeignKey('shopping_list.id', onupdate="CASCADE", ondelete='CASCADE'), primary_key=True))
class ShoppingItem(DeclarativeBase):
__tablename__ = "shopping_item"
id = Column(Integer, primary_key=True)
name = Column(String(50))
quantity = Column(String(5))
measure = Column(String(10))
# less important optional parameters that will be useful for users
brand = Column(String(50))
list_id = Column(Integer, ForeignKey('shopping_list.id'))
shopping_list = relation("ShoppingList", secondary=list_item_table, backref="items")
def get_owner_id(self):
return self.list.user_id
#classmethod
def delete_list(cls, id, user_id):
item = DBSession.query(cls).filter_by(id=id).one() # get the item from the given ID
if item.get_owner_id() == user_id: # owned by current user
DBSession.delete(item) # delete from shopping list
return True
flash(_("You do not have authorization to perform that action."))
return False
class ShoppingList(DeclarativeBase):
__tablename__ = 'shopping_list'
id = Column(Integer, primary_key=True)
date = Column(Date, index=True, nullable=False)
static = Column(Boolean, nullable=False, default=False)
# static is true if the items from the meal plan have been imported into the shopping list. Once done you can edit
# the items in the shopping list, remove items, etc. Until the shopping list is made static it is impossible to edit
# the items that are imported from the schedule as they do not exist in the shopping list! (and we do not want to
# edit them in the recipe!
user_id = Column(Integer, ForeignKey('tg_user.user_id'))
user = relation("User", backref="shopping_lists")
date_user_list = Index('date_user_list', 'date', 'user_id')

Maybe it's the list_id = Column(Integer, ForeignKey('shopping_list.id')) in the ShoppingItem model class that's confusing SQLAlchemy?

Related

flask sqlalchemy one-to-many filter, count & grouping

I am currently trying to build a query which
give me for a one-to-many sqlalchemy query in flask both my result filters grouped and then says how many individual entries there are for it
Following is my database model to illustrate the question:
class cellphone(db.Model):
__tablename__ = 'cellphone'
id = db.Column(db.Integer, primary_key = True)
number = db.Column(db.String(30), unique=True)
sms = db.relationship('sms_accounting', backref="cellphone", lazy='dynamic')
class sms_accounting(db.Model):
__tablename__ = 'sms_accounting'
id = db.Column(db.Integer, primary_key = True)
timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)
cellphone_id = db.Column(db.Integer, db.ForeignKey('cellphone.id'))
What I want to do now is find out how many SMS were sent within X days per number.
Filtering and grouping I managed to do, but to calculate the sum per device correctly is not possible.
def sms_count():
search_peroid='90' #time to fetch events in days
period_start = datetime.utcnow() - timedelta(hours=int(search_peroid))
phone_count = sms_accounting.query.filter(sms_accounting.timestamp.between(period_start, \
datetime.utcnow() )).group_by(sms_accounting.cellphone_id).all()
I found some examples for func.count, but unfortunately none of them works. This already starts with the usage,
AttributeError: BaseQuery object has no attribute 'func'
even though it was imported especially.
from sqlalchemy.sql.functions import func
Forgive me if I am wrong.
As an option, you could try executing an SQL Query through Flask.
db.session.execute('select number, count(sms_accounting.id) from cellphone join sms_accounting on sms_accounting.cellphone_id = cellphone.id');
You can easily add the time based filter using where.
Regarding the AttributeError, are you sure you are using the 'func' method properly? The correct usage can be found on this unrelated answer at https://stackoverflow.com/a/4086229/4854064. It might be that you accidentally called func as a method of the query object.

Have a custom primary key for users.id in flask-security

I want to have a custom key for the field id, for example, id_user, I've tried the following
class UserModel(db.model, UserMixin)
...
#property
def id(self):
return self.id_user
But couldn't make it work. When I try to login it sends me this message:
{
"message": "You don't have the permission to access the requested resource. It is either read-protected or not readable by the server."
}
I ended up with a nasty solution. I cloned the UserModel object, added a duplicated field for id, with the custom key I needed and told Flask-Security to use that object as the UserModel This is the function code I used:
def clone_model(model):
data = model
attr = getattr(model, "id_user")
setattr(data, "id", attr)
return data
cUserModel = clone_model(UserModel)
user_datastore = SQLAlchemyUserDatastore(db, cUserModel, Roles)
security = Security(app, user_datastore)
Hope someone find it useful
For anybody who is using Flask-Security-Too looking to change the default id column, here is my solution.
Define the User and Role Tables as usual and just define the id column depending on your preference.
class Role(db.Model, FsRoleMixin):
__tablename__ = 'role'
id = Column(String, primary_key=True)
class Users(db.Model, FsUserMixin):
__tablename__ = 'users'
id = Column(String, primary_key=True)
For me, I need to use String primary_key Column. For that to I needed to change the many-to-many relationship table (roles_users). Below is the code snippet for the same.
# Initialize the db object
db = SQLAlchemy()
# import this Class
from flask_security.models.fsqla_v2 import FsModels
# call this after creating the db object
FsModels.db = db
FsModels.user_table_name = 'users' # If you want a different table name than the default
FsModels.role_table_name = 'role'
# Create the relationship table as per your preferences
FsModels.roles_users = db.Table(
"roles_users",
Column("user_id", String, ForeignKey("users.id")),
Column("role_id", String, ForeignKey("role.id")),
)

SQLAlchemy many-to-many relationship updating association object with extra column

In my application, a 'set' can have a number of 'products' associated with it. Products listed against a set must have quantities defined. For this many-to-many relationship I have followed the SQLAlchemy documentation to use an association table with an additional column (quantity).
I am trying to create a form where the user can assign products and quantities against a given set. Both the sets and products already exist in the database. The data from the form are:
set.id
product.id
quantity
This works to create a new association (e.g. set 1 is 'linked' to product 3 with quantity=XYZ) but I get an integrity error when I try to update an existing record.
I can manually add a relationship/record (dummy data) or within the Flask view function as follows:
s = Set.query.get(2)
p = Product.query.get(3)
a = Set_Product_Association(set=s, product=p, quantity=23)
db.session.add(a)
db.session.commit()
Updating the record (different quantity) manually as follows works:
s.products[0].quantity = 43
db.session.add(s)
db.session.commit()
However when I use the code from the first block instead (with the aim to update the quantity field for a given, existing set and product ID), i.e.:
a = Set_Product_Association(set=s, product=p, quantity=43)
I get an integrity error
sqlalchemy.exc.IntegrityError: (sqlite3.IntegrityError) UNIQUE constraint failed: set_product_association.set_id, set_product_association.product_id [SQL: 'INSERT INTO set_product_association (set_id, product_id, quantity) VALUES (?, ?, ?)'] [parameters: (2, 3, 43)]
I assume this is to tell me that I'm trying to append a new record rather than updating the existing one.
How should I approach this? The 'manual' method works but relies on working out the correct index in the list (i.e. for the correct product.id).
Curiously, if I use form.popluate_obj(set) in my Flask view function to process the form data as described in my question here, I can update fields but not create new 'associations'. Unfortunately, I don't know what goes on behind the scenes there....
My models are defined like so:
class Set_Product_Association(db.Model):
__tablename__ = 'set_product_association'
set_id = db.Column(db.Integer, db.ForeignKey('sets.id'), primary_key=True)
product_id = db.Column(db.Integer, db.ForeignKey('products.id'), primary_key=True)
quantity = db.Column(db.Integer)
product = db.relationship("Product", back_populates="sets")
set = db.relationship("Set", back_populates="products")
class Set(db.Model):
__tablename__ = 'sets'
id = db.Column(db.Integer, primary_key=True)
products = db.relationship("Set_Product_Association",
back_populates="set")
class Product(db.Model):
__tablename__= 'products'
id = db.Column(db.Integer, primary_key=True)
part_number = db.Column(db.String(100), unique=True, nullable=False)
sets = db.relationship("Set_Product_Association",
back_populates="product")
Edit:
I've also tried reversing the operation as suggested here:
s = Set.query.get(2)
a = Set_Product_Association()
a.quantity = 43
a.product = Product.query.get(3)
a.set = s
db.session.commit()
But I still get an error:
sqlalchemy.exc.IntegrityError: (sqlite3.IntegrityError) UNIQUE constraint failed: set_product_association.set_id, set_product_association.product_id [SQL: 'INSERT INTO set_product_association (set_id, product_id, quantity) VALUES (?, ?, ?)'] [parameters: (2, 3, 43)]
You get an integrity error because you are trying to create a new object with the same primary keys.
This:
a = Set_Product_Association(set=s, product=p, quantity=43)
Does not update, but create.
If you want to update the actual row in the table, you need to update the existing one:
assoc = Set_Product_Association.query.filter_by(set=s, product=p).first()
assoc.quantity = 43
db.session.commit()
Also, from the documentation it is advised to not use a model but an actual table.

Assigning value to foreign key field

I'm having difficulty assigning a title to the UserService model, which is a foreign key to another model.
models.py
class IndustryService(models.Model):
industryname = models.ForeignKey(Industry, on_delete=models.CASCADE)
title = models.CharField(max_length=120)
class UserService(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
title = models.ForeignKey(IndustryService, on_delete=models.CASCADE)
Here is the portion of code within the view that is failing:
industryservices = IndustryService.objects.filter(industryname=industry)
for service in industryservices:
try:
qs = UserService.objects.get(user=user, title=service.title)
except:
userserv = UserService.objects.create(user=request.user)
userserv.title = service
userserv.save()
The error that I'm getting is as follows:
NOT NULL constraint failed: accounts_userservice.title_id
Based on my testing, the way in which I'm assigning the value to the 'title' foreign key field is wrong (i.e. these 2 lines of code).
service2 = IndustryService.objects.get(title=service.title)
userserv.title = service2
Any thoughts on how I can fix this? Thanks!
You're doing two updates, unnecessarily. Either create the item in one go:
userserv = UserService.objects.create(user=request.user, title=service)
or instantiate without saving and then save at the end:
userserv = UserService(user=request.user)
userserv.title = service
userserv.save()

Django - processing objects after .filter(...) or .all(...)

Still learning Django, so not sure if there's a nice way to do this.
I have a few models with specific attributes (all use Item as base class), and a metadata table (id, language, type, value) used to store any extra attributes that could be potentially associated with instances of any of those models (code below). These models are used with a form / template, simple web-based CRUD.
Right now, I call .save_metadata(...) and .load_metadata(...) explicitly, and use .form_initia(...) to populate the form with metadata that isn't explicitly in the model.
I'm looking for a way to handle this automatically -- basically implementing a model with a variable number of fields, key ones are columns in the model's table, the other ones are rows in the metadata table, and are instance-specific. Is there a way of hooking a method after objects.get(...) or objects.filter(...) etc? I've messed with custom managers and looked into signals, but nothing seems to lead towards an acceptable solution.
class Item(models.Model):
mdata = ['title'] # metadata associated with item
user = models.ForeignKey(User)
created = models.DateTimeField(auto_now_add = True)
status = models.IntegerField(default=0, choices = ([(0,'Staged'), (1,'Published'),(2,'Archived'), ]))
def set_status(self, s):
self.status = s
self.save()
# stores metadata attributes associated with current item
def save_metadata(self, lang, form):
for mt in self.mdata:
try:
md = Metadata.objects.get(item=self, lang=lang, t=mt)
except Metadata.DoesNotExist:
md = Metadata.objects.create(item=self, lang=lang, t=mt)
md.v=form.cleaned_data[mt]
md.save()
# retrieves metadata attributes associated with current item
def load_metadata(self, lang):
for mt in self.mdata:
self.__dict__[mt] = None
try:
self.__dict__[mt] = Metadata.objects.get(item=self, t=mt, lang=lang).v
except Metadata.DoesNotExist:
md = Metadata.objects.filter(item=self, t=mt)
if len(md) > 0:
self.__dict__[mt] = md[0].v
# provides metadata attributes associated with current item needed to populate a form
def form_initial(self, seed=None):
meta = {}
for mt in self.mdata:
meta[mt] = self.__dict__[mt]
#meta[mt] = 'test'
if seed:
meta = dict(meta.items() + seed.items())
return meta
# used to store various metadata associated with models derived from Item
class Metadata(models.Model):
item = models.ForeignKey(Item)
lang = models.CharField(max_length = 8)
t = models.CharField(max_length = 250)
v = models.CharField(max_length = 2500)