0

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
halfer
  • 19,824
  • 17
  • 99
  • 186
ravioli
  • 3,749
  • 3
  • 14
  • 28

1 Answers1

1

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

markwalker_
  • 12,078
  • 7
  • 62
  • 99
  • Thanks Mark, this is great! I changed the MyUser.save() function and was able to sub-class MyUserManager finally. class MyUserManager(**BaseUserManager**): ... class MyUser(AbstractBaseUser): def save(self): return **MyUser**.MyUserMgr.upsertUser(self) That leaves me with two questions: 1) In the save() function, why do I have to refer to the class (MyUser) instead of the object (self)? 2) Aside from the helper functions (all, get, etc.) is there an advantage to aliasing my manager as "objects" instead of UserDataManager? – ravioli Nov 11 '17 at 00:29
  • Ok, first, have a look at this answer about model managers; https://stackoverflow.com/a/3874563/1199464 When it comes to using `objects` as the manager attribute, it's just the standard reference in django, so your code will make more sense to other people & when you read similar in other people's code you'll pick it up quicker. I used to do things like `cache = MyCachedManager()` so that kept cached querysets etc. So you can use other attribute names. (also, keep those references lower case so `User.userdatamanager`, rather than `User.UserDataManager`. Again that's just more pythonic. – markwalker_ Nov 11 '17 at 12:15
  • Yes, I'm starting to see the usefulness of the "objects" notation. I updated all my references and it's starting to feel more natural now. And all of the Instance/Class stuff too is starting to sink in...slowly. Thanks for the tips, I appreciate it. – ravioli Nov 12 '17 at 00:41