I have a data model which has a column that depends on other column values, following the instructions in this page I've created a context-sensitive function which is used to determine the value of this particular column on creation, something like this:
def get_column_value_from_context(context):
# Instructions to produce value
return value
class MyModel(db.Model):
id = db.Column(db.Integer,
primary_key=True)
my_column = db.Column(db.String(64),
nullable=False,
default=get_column_value_from_context)
name = db.Column(db.String(32),
nullable=False,
unique=True,
index=True)
title = db.Column(db.String(128),
nullable=False)
description = db.Column(db.String(256),
nullable=False)
This approach works pretty decent, I can create rows without problems from the command line or using a script.
I've also added a ModelView to the app using Flask-Admin:
class MyModelView(ModelView):
can_view_details = True
can_set_page_size = True
can_export = True
admin.add_view(MyModelView(MyModel, db.session))
This also works pretty decent until I click the Create button in the list view. I receive this error:
AttributeError: 'NoneType' object has no attribute 'get_current_parameters'
Because the implementation of the create_model handler in the ModelView is this:
def create_model(self, form):
"""
Create model from form.
:param form:
Form instance
"""
try:
model = self.model()
form.populate_obj(model)
self.session.add(model)
self._on_model_change(form, model, True)
self.session.commit()
except Exception as ex:
if not self.handle_view_exception(ex):
flash(gettext('Failed to create record. %(error)s', error=str(ex)), 'error')
log.exception('Failed to create record.')
self.session.rollback()
return False
else:
self.after_model_change(form, model, True)
return model
and here there isn't a context when the model is instantiated. So, I've created a custom view where the model instantiation in the creation handler could be redefined:
class CustomSQLAView(ModelView):
def __init__(self, *args, **kwargs):
super(CustomSQLAView, self).__init__(*args, **kwargs)
def create_model(self, form):
"""
Create model from form.
:param form:
Form instance
"""
try:
model = self.get_populated_model(form)
self.session.add(model)
self._on_model_change(form, model, True)
self.session.commit()
except Exception as ex:
if not self.handle_view_exception(ex):
flash(gettext('Failed to create record. %(error)s', error=str(ex)), 'error')
log.exception('Failed to create record.')
self.session.rollback()
return False
else:
self.after_model_change(form, model, True)
return model
def get_populated_model(self, form):
model = self.model()
form.populate_obj(model)
return model
Now I can redefine the get_populated_model method to instantiate the model in the usual way:
class MyModelView(CustomSQLAView):
can_view_details = True
can_set_page_size = True
can_export = True
def get_populated_model(self, form):
model = self.model(
name=form.name.data,
title=form.title.data,
description=form.description.data,
)
return model
Despite that this works, I suspect it breaks something. Flask-Admin has several implementation of the populate_obj method of forms and fields, so I would like to keep everything safe.
What is the proper way to do this?
So there are several factors which come in to play at this question.
But first, a small fully functional app to illustrate:
from flask_admin.contrib.sqla import ModelView
from flask_sqlalchemy import SQLAlchemy
from flask_admin import Admin
from flask import Flask
app = Flask(__name__)
db = SQLAlchemy(app)
admin = Admin(app)
app.secret_key = 'arstartartsar'
def get_column_value_from_context(context):
print('getting value from context...')
if context.isinsert:
return 'its an insert!'
else:
return 'aww, its not an insert'
class MyModel(db.Model):
id = db.Column(db.Integer, primary_key=True)
my_column = db.Column(db.String(64), nullable=False, default=get_column_value_from_context)
name = db.Column(db.String(32), nullable=False, unique=True, index=True)
class MyModelView(ModelView):
form_excluded_columns = ['my_column']
db.create_all()
admin.add_view(MyModelView(MyModel, db.session))
print('1')
x = MyModel(name='hey')
print('2')
db.session.add(x)
print('3')
db.session.commit()
print('4')
When we fire up this app, what's printed is:
1
2
3
getting value from context...
4
So, only after committing, does the get_column_value_from_context function fire up. When, in the create view, you 'create' a new model, you don't have a context yet because you're not committing anything to the database yet. You only get the context to set the default when you're committing your instance to the database! That why, in the source code of flask admin, this happens:
if getattr(default, 'is_callable', False):
value = lambda: default.arg(None) # noqa: E731
They check if the default you specified is a function, and if so, they call it without context (because there is no context yet!).
You have several options to overcome this depending on your goals:
1) Only calculate the value of my_column when adding it to the database:
Simply ignore the field in the form, and it will be determined when you add it to the db.
class MyModelView(ModelView):
form_excluded_columns = ['my_column']
2) Only calculate it when adding it to the db, but making it editable afterwards:
class MyModelView(ModelView):
form_edit_rules = ['name', 'my_column']
form_create_rules = ['name']
Related
i've two tables: billtexts and zahlungsarten (kinds of payment).
every billtext has it's own zahlungsart (kind of payment).
zahlungsarten is just a selectiontable - to select the zahlungsarten for the billtext entries from.
by adding new billtexts (or modifying one) the zahlungsart entry is selectable in a selection field.
i can add new billtexts, everything works fine. but when i add a new zahlungsart (or modify one) to zahlungsarten (the selectiontable), then return to the billtext form and try to add/edit a billtext, the new zahlungsart is not shown in the selectionfield of the billtexts at runtime (allthough its in database!). If i restart the app it is shown correctly.
using a function insead of the Mixin to generate the selection_values makes no difference...
should not fetch get_selection_values the actual data at runtime?
Do I have to update something at runtime to get fresh inserted/modified data for selection-fields?
class SelZahlungsart(db.Model, GetSelectionValuesMixin):
__tablename__ = 'sel_zahlungsart'
idsel_zahlungsart = db.Column(db.SmallInteger, primary_key=True, info='id of the zahlungsart')
zahlungsart = db.Column(db.String(15), nullable=False, info='Bezeichnung der Zahlungsart')
class GetSelectionValuesMixin(object):
#staticmethod
def get_selection_values(q_object, q_field):
"""
Returns selection values of given table and column as a list.
:param q_object: the db.Model class
:param q_field: the db.Model class field
:return: all values from the given Model/field
"""
result = [r.field for r in db.session.query(q_object).with_entities(q_field.label('field')).distinct()]
#result = [r.field for r in q_object.query.with_entities(q_field.label('field')).distinct()]
return result
class TBilltext(db.Model):
__tablename__ = 't_billtexts'
textid = db.Column(db.Integer, primary_key=True)
...
zahlungsart = db.Column(db.String(15), nullable=False, unique=True, info='kind of payment')
class BilltextForm(FlaskForm):
"""
Form for admin to add/edit billextra
"""
...
zahlungsart = SelectField(choices=SelZahlungsart.get_selection_values(SelZahlungsart, SelZahlungsart.zahlungsart))
submit = SubmitField('Speichern')
and the view:
add_billtext = True
form = BilltextForm()
if form.validate_on_submit():
billtext = TBilltext(...,
zahlungsart=form.zahlungsart.data)
return (add_dataset(billtext, "Neuer Rechnungstext erfolgreich hinzugefügt",
"Fehler beim Hinzufügen des neuen Rechnungstextes!", "admin.list_billtexts"))
...
Seems like get_selection_values only runs once at application start up. So values can never be updated... But why does it not run each time its called?
I am using flask-security. I would like to set up #roles_accepted with a variable based upon the app route.
For example, if _id = Lon_2020 the #roles_accepted would allow Lon. This would grant certain users to projects, but not others, based on the _id.
#app.route('/<string:_id>')
#app.route('/<string:_id>/')
#roles_accepted('admin',_id[:3])
def home(_id):
return redirect(url_for('tb',_id=_id))
At the moment, this causes an error, because _id is not defined. The roles_accepted relies on the _id data.
If this is not possible, would you mind letting me know a better way of allowing certain users into certain projects. e.g.
Lon_2019
Lon_2020
Lon_2021
Par_2019
Par_2020
Par_2021
Ber_2018
Ber_2019
where the identified is the first 3 characters 'Lon','Par','Ber'
Any help is much appreciated. Thank you.
Wrap the #roles_accepted decorator with another decorator! In the code below the variable name to extract the role is being passed in the outer decorator although it could be hard-coded.
def roles_accepted_from_route(variable_name):
def decorator(func):
#wraps(func)
def wrapper(*args, **kwargs):
_variable = kwargs.get(variable_name, None)
if _variable:
print(_variable, _variable[:3])
#roles_accepted('admin', _variable[:3])
def inner_wrapper(*args, **kwargs):
print(f'roles_accepted returned True, variable: {_variable[:3]}')
return func(*args, **kwargs)
return inner_wrapper(*args, **kwargs)
return wrapper
return decorator
And use as follows:
#app.route('/<string:_id>')
#roles_accepted_from_route(variable_name='_id')
def some_route(_id):
return f'Route Variable: {_id}'
Single file example below, there is little error checking. A user with email: 'fred#example.net', password: 'password' with role 'LON' is created on the first Flask request.
from functools import wraps
from flask import Flask
from flask_security import roles_accepted
from flask_security.utils import hash_password
from flask_sqlalchemy import SQLAlchemy
from flask_security import Security, SQLAlchemyUserDatastore, UserMixin, RoleMixin, current_user
app = Flask(__name__)
app.config['DEBUG'] = True
app.config['SECRET_KEY'] = 'super-secret'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.db3'
app.config['SECURITY_PASSWORD_SALT'] = '5ce39dc7add2284076de45b923d74dd00a052117cdf0ab900548565681e56fce'
# Create database connection object
db = SQLAlchemy(app)
# Define models
roles_users = db.Table('roles_users',
db.Column('user_id', db.Integer(), db.ForeignKey('user.id')),
db.Column('role_id', db.Integer(), db.ForeignKey('role.id')))
class Role(db.Model, RoleMixin):
id = db.Column(db.Integer(), primary_key=True)
name = db.Column(db.String(80), unique=True)
description = db.Column(db.String(255))
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(255), unique=True)
password = db.Column(db.String(255))
active = db.Column(db.Boolean())
confirmed_at = db.Column(db.DateTime())
roles = db.relationship('Role', secondary=roles_users,
backref=db.backref('users', lazy='dynamic'))
# Setup Flask-Security
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
security = Security(app, user_datastore)
# Create a user to test with
#app.before_first_request
def create_user():
db.drop_all()
db.create_all()
_lon_role = user_datastore.create_role(name='LON')
_user = user_datastore.create_user(email='fred#example.net', password=hash_password('password'))
user_datastore.add_role_to_user(_user, _lon_role)
db.session.commit()
def roles_accepted_from_route(variable_name):
def decorator(func):
#wraps(func)
def wrapper(*args, **kwargs):
_variable = kwargs.get(variable_name, None)
if _variable:
print(_variable, _variable[:3])
#roles_accepted('admin', _variable[:3])
def inner_wrapper(*args, **kwargs):
print(f'roles_accepted returned True, variable: {_variable[:3]}')
return func(*args, **kwargs)
return inner_wrapper(*args, **kwargs)
return wrapper
return decorator
#app.route('/')
def home():
if current_user.is_authenticated:
return f'You are logged in as : {current_user.email} and have roles: {",".join([r.name for r in current_user.roles])}'
else:
return f'Login'
#app.route('/<string:_id>')
#roles_accepted_from_route(variable_name='_id')
def some_route(_id):
return f'Route Variable: {_id}'
if __name__ == '__main__':
app.run()
Below is the documentation from Flask-Security library for roles_accepted decorator.
flask_security.decorators.roles_accepted(*roles):
Decorator which specifies that a user must have at least one of the specified roles. Example:
#app.route('/create_post')
#roles_accepted('editor', 'author')
def create_post():
return 'Create Post'
In order to use this decorator, you need to first create the roles in your role table, and user your model and role model needs to follow the certain format so that Flask-Security library can recognize existing roles.
Model Example
# association table between user and role
roles_users = db.Table(
'roles_users',
db.Column('user_id', db.Integer(), db.ForeignKey('user.id')),
db.Column('role_id', db.Integer(), db.ForeignKey('role.id'))
)
# role table
class Role(db.Model, RoleMixin):
__tablename__ = 'role'
id = db.Column(db.Integer(), primary_key=True)
name = db.Column(db.String(30), unique=True)
# user table
class User(db.Model, UserMixin):
__tablename__ = 'user'
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(50), unique=True)
password = db.Column(db.String(255))
Once you've specified the mode such as above, then you create the role by inserting into role table. Once you create editor and author roles like shown above in the view, when the user with either editor or author role come in to /create_post view, it will be able to control the access-control based on roles.
If you are getting the ID of user or role from above, and want to perform access-control based on that (which I think it is unnecessary) because Flask-Security has current_user variable where you can parse out all information about that user without you manually making the query).
You will have to first make the query using the ID to user or role table, then based on that, you will either throw 403 or allow the content to return which then you won't require to have your own #roles_accepted('editor', 'author'), because you are doing that manually.
In a case like this - don't use the decorator - in your app.route - have the first couple lines calculate the 'role' then call the decorator directly such as:
#app.route("/createpost")
#auth_required()
def createpost():
/// figure out what role based on current_user or whatever
return roles_required("author")(domyview)()
def domyview():
// Actual code for endpoint here.
I have a solution which is working, but isn’t perfect. Based on #Jessi suggestion of 403 abort:
def check_user(c_id):
if current_user.has_role('admin'):
current = c_id
to_check = ['Lon','Par’,’Ber'] #manual inserton of roles from db, could use sqlalchemy to query db instead?
for n in to_check:
if current_user.has_role(n):
current = n
if c_id == current:
return True
#app.route('/<string:_id>')
#app.route('/<string:_id>/')
def home(_id):
c_id = _id[:3])
if check_user(c_id):
return redirect(url_for('tb',_id=_id))
else:
return abort(403)
I have some tests that were working when I ran them with regular database objects but are broken now that I am using FactoryBoy factories. I think I understand why they are broken but am struggling with the correct way to set this up.
Here are my factories:
#register
class UserFactory(BaseFactory):
"""User factory."""
username = Sequence(lambda n: 'user{0}'.format(n))
email = Sequence(lambda n: 'user{0}#example.com'.format(n))
password = PostGenerationMethodCall('set_password', 'example')
active = True
class Meta:
"""Factory configuration."""
model = User
#register
class ExperimentFactory(BaseFactory):
"""Experiment Factory."""
date = fake.date_this_decade(before_today=True, after_today=False)
scanner = Iterator(['GE', 'Sie', 'Phi'])
class Meta:
"""Factory configuration."""
model = Experiment
user = factory.SubFactory(UserFactory)
According to this answer and other examples, FactoryBoy is supposed to be handling the foreign key assignment behind the scenes.
But when I try to initialize my ExperimentFactory object in my fixture, I have a problem.
#pytest.fixture(scope='function')
#pytest.mark.usefixtures('db')
def mocked_scan_service(db, mocker, request):
user = UserFactory(password='myprecious')
db.session.add(user)
num_exp, num_scans, exp_id, scan_id, exp_uri, scan_uri = request.param
for i in range(num_exp):
experiment = ExperimentFactory(user_id = user.id)
db.session.add(experiment)
db.session.commit()
ss = ScanService(user.id, experiment.id)
for i in range(num_scans):
ss._add_scan_to_database()
ss.xc.upload_scan = mocker.MagicMock()
ss.xc.upload_scan.return_value = ('/data/archive/subjects/000001', exp_uri, scan_uri)
mocker.spy(ss, '_generate_xnat_identifiers')
ss.param = request.param
return ss
If I don't pass ExperimentFactory a user id, I get this error:
TypeError: __init__() missing 1 required positional argument: 'user_id'
Here's the model; it makes sense to me that the factory needs an argument user_id to initialize:
class Experiment(SurrogatePK, Model):
"""A user's experiment, during which they are scanned."""
__tablename__ = 'experiment'
date = Column(db.Date(), nullable=False)
scanner = Column(db.String(80), nullable=True)
num_scans = Column(db.Integer(), nullable=True, default=0)
xnat_experiment_id = Column(db.String(80), nullable=True)
xnat_uri = Column(db.String(80), nullable=True)
user_id = reference_col('user', nullable=False)
scans = relationship('Scan', backref='experiment')
def __init__(self, date, scanner, user_id, **kwargs):
"""Create instance."""
db.Model.__init__(self, date=date, scanner=scanner, user_id=user_id, **kwargs)
def __repr__(self):
"""Represent instance as a unique string."""
return '<Experiment({date})>'.format(date=self.date)
But if, as written, I explicitly create a user and then pass the user id, it looks like the ExperimentFactory eventually overwrites the foreign key with the SubFactory it generated. So later when I initialize an object called ScanService which must be initialized with a user_id and and experiment_id, my tests fail for one of two reasons. Either I initialize it with the user_id of my explicitly created user, and my tests fail because they don't find any sibling experiments to the experiment that experiment_id belongs to, or I initialize it with experiment.user.id, and my tests fail because they expect one user in the database, and in fact there are two. That latter problem would fairly easy to work around by rewriting my tests, but that seems janky and unclear. How am I supposed to initialize the ExperimentFactory when the Experiment model requires a user_id for initialization?
If anyone has a better solution feel free to comment, but here's what I realized: it really doesn't matter what I pass in for user_id; I just have to pass in something so that model initialization doesn't fail. And passing in user=user at the same time creates the situation I want: all experiments belong to the same user. Now all my tests pass. Here's the modified fixture code; everything else remained the same:
#pytest.fixture(scope='function')
#pytest.mark.usefixtures('db')
def mocked_scan_service(db, mocker, request):
num_exp, num_scans, exp_id, scan_id, exp_uri, scan_uri = request.param
user = UserFactory(password='myprecious')
for i in range(num_exp):
experiment = ExperimentFactory(user_id=user.id, user=user)
db.session.add(experiment)
db.session.commit()
ss = ScanService(experiment.user.id, experiment.id)
for i in range(num_scans):
ss._add_scan_to_database()
ss.xc.upload_scan = mocker.MagicMock()
ss.xc.upload_scan.return_value = ('/data/archive/subjects/000001', exp_uri, scan_uri)
mocker.spy(ss, '_generate_xnat_identifiers')
ss.param = request.param
return ss
I am making a flask restful api, what I'm having trouble with is marshmallow-sqlalchemy, and webargs
in short here is my sqlalchemy model:
class User(Model):
id = Column(String, primary_key=True)
name = Column(String(64), nullable=False)
email = Column(String(120), nullable=False)
password = Column(String(128))
creation_date = Column(DateTime, default=datetime.utcnow)
and this is my schema
class UserSchema(ModelSchema):
class Meta:
model = User
strict = True
sqla_session = db.session
user_schema = UserSchema()
and an example of my routes using flask-classful and webargs:
class UserView(FlaskView):
trailing_slash = False
model = User
schema = user_schema
#use_kwargs(schema.fields)
def post(self, **kwargs):
try:
entity = self.model()
for d in kwargs:
if kwargs[d] is not missing:
entity.__setattr__(d, kwargs[d])
db.session.add(entity)
db.session.commit()
o = self.schema.dump(entity).data
return jsonify({'{}'.format(self.model.__table__.name): o})
except IntegrityError:
return jsonify({'message': '{} exist in the database. choose another id'
.format(self.model.__table__.name)}), 409
#use_kwargs(schema.fields)
def put(self, id, **kwargs):
entity = self.model.query.filter_by(id=id).first_or_404()
for d in kwargs:
if kwargs[d] is not missing:
entity.__setattr__(d, kwargs[d])
db.session.commit()
o = self.schema.dump(entity).data
return jsonify({'{}'.format(self.model.__table__.name): o})
UserView.register(app)
The problem:
as you can see in my sqlalchemy model, some fields are not nullable, thus my marshmallow schemda marks them as required. My get, index, delete and post methods all work perfectly. But I included post for one reason:
when I try to post a new user with no name for example, a 422 http code is raised because name field is required, which is something I want and it is done perfectly.
BUT when editing fields with put request, I wish EVERYTHING in my schema becomes optional.. right now if I wanted to update a user, I must provide not only the id.. but ALL other information required by default even if I didn't change them at all.
in short, how to mark all fields as "optional" when the method is "put"?
EDIT: just like the solution provided by #Mekicha, I made the following chnages:
change the schema to make the required fields in my model accept the value None. like this:
class UserSchema(ModelSchema):
class Meta:
model = User
...
name = fields.Str(missing=None, required=True)
email = fields.Email(missing=None, required=True)
...
change my put and post method condition from this:
if kwargs[d] is not missing:
to this:
if kwargs[d] is not missing and kwargs[d] is not None:
Since you want to make the fields optional during put, how about setting the missing attribute for the fields. From the doc:
missing is used for deserialization if the field is not found in the
input data
I think a combination of missing and allow_none(which defaults to True when missing=None) as pointed out here: https://github.com/marshmallow-code/marshmallow/blob/dev/src/marshmallow/fields.py#L89 should work for you
I've hacked my way to getting my code to work, but I'm pretty sure I'm not doing it as it was intended.
My constraint is I want to have separate DB and UI layers, so I have all the DB-logic encapsulated in SPs/functions that are called from Django's view layer. I tried doing this using the included managers, but kept getting this error:
Manager isn't accessible via %s instances" % cls.__name__)
So, I just removed the manager sub-class and kept going. It works with some extra hacks, but it doesn't feel right. My question is, how do I get my code to work, but still inheriting the stuff from the appropriate managers (i.e. BaseUserManager)? Here's the code:
models.py
from __future__ import unicode_literals
from django.db import models
from UsefulFunctions.dbUtils import *
from django.contrib.auth.models import AbstractBaseUser
class MyUserManager():
# Bypassing BaseUserManager which includes these methods: normalize_email, make_random_password, get_by_natural_key
# Create new user
def create_user(self, password, usertype = None, firstname = None, lastname = None, phonenumber = None, emailaddress = None):
user = MyUser( # TO-DO: Replace MyUser with "get_user_model" reference
userid=None,
usertype=usertype,
firstname=firstname,
lastname=lastname,
phonenumber=phonenumber,
emailaddress=emailaddress
)
# Hash and save password
user.set_password(password)
# Save user data
user.save()
return user
def upsertUser(self, myUser):
return saveDBData('SP_IGLUpsertUser',
(
myUser.userid,
myUser.usertype,
myUser.firstname,
myUser.lastname,
myUser.phonenumber,
myUser.emailaddress,
myUser.password,
myUser.last_login,
None,
)
)
# Create custom base user
class MyUser(AbstractBaseUser):
# Define attributes (inherited class includes password + other fields)
userid = models.IntegerField(unique=True)
usertype = models.CharField(max_length=2)
firstname = models.CharField(max_length=100)
lastname = models.CharField(max_length=100)
phonenumber = models.CharField(max_length=25)
emailaddress = models.CharField(max_length=250)
# Define data manager
MyUserMgr = MyUserManager()
# Create new constructor
def __init__(self, userid = None, usertype = None, firstname = None, lastname = None, phonenumber = None, emailaddress = None):
super(MyUser, self).__init__() # TO-DO: Convert MyUser to get_user_model()
self.userid = userid
self.usertype = usertype
self.firstname = firstname
self.lastname = lastname
self.phonenumber = phonenumber
self.emailaddress = emailaddress
# Define required fields for AbstractBaseUser class
USERNAME_FIELD = 'userid' # specify how Django recognizes the user
EMAIL_FIELD = 'emailaddress'
REQUIRED_FIELDS = ['usertype','firstname','lastname'] # email and password are required by default
# Define class meta info
class Meta:
managed = False
db_table = 'userprofile'
# Required methods
def get_full_name(self):
return self.firstname + " " + self.lastname + " (" + self.userid + ")"
def get_short_name(self):
return self.userid
def save(self):
return self.MyUserMgr.upsertUser(self)
# Define model managers (interface between DB and objects)
class ItemDataManager():
def getAllItems(self):
return getDBData('SP_IGLGetItem', (None,)) # Use tuple instead of array for input parameters
def getItem(self, myItem):
return getDBData('SP_IGLGetItem', (myItem.itemid,))
def getItemDetail(self, myItem):
return getDBData('SP_IGLGetItemDetail', (myItem.itemid,))
def upsertItem(self, myItem):
return saveDBData('SP_IGLUpsertItem',
(
myItem.itemid,
myItem.itemname,
myItem.itemdescription,
myItem.itemcontactuserid,
)
)
def deleteItem(self, myItem):
return deleteDBData('SP_IGLDeleteItem', (myItem.itemid, None))
# Define data models (i.e. tables)
class Item(models.Model):
# Model properties
itemid = models.IntegerField
itemname = models.CharField(max_length=100)
itemdescription = models.CharField(max_length=5000)
itemcontactuserid = models.IntegerField
# Create Item Data Manager instance
myItemMgr = ItemDataManager()
# Create new constructor
def __init__(self, itemid = None):
super(Item, self).__init__()
self.itemid = itemid
# Define static methods (don't depend on object instance)
#staticmethod
def get_all():
return ItemDataManager().getAllItems()
# Define instance methods
def get(self):
return self.myItemMgr.getItem(self)
# Define instance methods
def get_detail(self):
return self.myItemMgr.getItemDetail(self)
def save(self):
return self.myItemMgr.upsertItem(self)
def delete(self):
return self.myItemMgr.deleteItem(self)
Sample call:
from django.contrib.auth import get_user_model;
get_user_model().MyUserMgr.create_user('mypass','AD','Joe','Smith','1233','joe#smith.com')
This is the line that's giving me trouble:
def save(self):
return self.MyUserMgr.upsertUser(self)
Right now, it works fine. But when I subclass BaseUserManager, I can't get it to work. What am I doing wrong? How should I restructure the code/references to properly use the included manager classes?
I've read all the relevant posts. I'm guessing the answer is in there somewhere, but it's all a jumbled mess to me at this point.
I am using:
Django 1.11
Python 2.7
Postgres 9.6
The error is caused by you trying to access the model manager from the instance.
In save() you're dealing with an instance of the model to be saved, so you can't access the manager. self is an instance (object) of the class, not the class itself.
First of all, I'd swap to the standard django approach with your manager which would be objects = MyUserMgr() so then you can do MyUserModel.objects.all(), MyUserModel.objects.upsertUser() etc.
Normally in Django you'd use a model manager to run queries that you want to use a lot so that you don't have to duplicate them in your views/forms etc.
Then you can just stick to saving the instance in the model save() method to start to try to simplify what you're doing because you've got quite complex already.
Have a look at the docs for Managers; https://docs.djangoproject.com/en/1.11/topics/db/managers/#managers
Then have a look at this really simple approach to extending the user model https://simpleisbetterthancomplex.com/tutorial/2016/07/22/how-to-extend-django-user-model.html