Python can't set attribute on non-persistent property - python

I want to set a non-persistent property on a model. I have tried the following:
class class User(models.Model):
email = models.EmailField(max_length=254, unique=True, db_index=True)
#property
def client_id(self):
return self.client_id
Then:
user = User.objects.create(email='123', client_id=123)
print(user.client_id)
I get the error: can't set attribute. Why?

You need to define a setter function for your property too, right now, it is read-only (see, e.g., this question).
class class User(models.Model):
email = models.EmailField(max_length=254, unique=True, db_index=True)
#property
def client_id(self):
return self.internal_client_id
#client_id.setter
def client_id(self, value):
self.internal_client_id = value
Note that I renamed self.client_id to self.internal_client_id, because otherwise, you would be calling the getter and setter functions recursively - the name of the internal variable needs to be different from the property's name.
Of course, if you need to use self.client_id (e.g. because of inheritance), you can also rename the property itself.

Related

Will the delete function be triggered if on_delete is set to models.PROTECT?

I am trying to implement a behavior where when I am trying to delete an instance, the instance will not be deleted but django will set an attribute called deleted to True.
However, when I am trying to define a foreign key, I have to set on_delete because it is required. I set it to models.PROTECT. My question is: Will django trigger my overridden delete function while setting on_delete to models.PROTECT?
Here is an example code:
class BaseModel(models.Model):
deleted = models.BooleanField(default=False)
def delete(self, using=None, keep_parents=False):
self.deleted = True
self.save()
class A(BaseModel):
pass
class B(BaseModel):
a = models.ForeignKey('A', on_delete=models.PROTECT)

How to access instantiated attribute of django model

models.py
from django.db import models
from alpha_id import get_alpha_id
class Sample(models.Model):
alpha_id = get_alpha_id(self.id)
sample_name = models.CharField(max_length=30)
entry_date = models.DateField(auto_now_add=True)
def __unicode__(self):
return self.alpha_id
alpha_id.py
import string
ALL_LETTERS = string.ascii_uppercase.replace('F', '').replace('I', '').replace('L', '').replace('O', '').replace('V', '')
def get_alpha_id(id):
""" Return the alpha numeric ID according to the current
integer id.
"""
global ALL_LETTERS
alpha = ALL_LETTERS[(id%len(ALL_LETTERS))-1]
return str(id) + '_' + alpha
Here, I am trying to create a alpha_id model attribute which establishes an alpha numeric id based on the automatically created integer id attribute. I wrote a function that performs the algorithm, and I just need to send that method the id of the current instantiated model. For example:
>>> get_alpha_id(1)
1_A
>>>get_alpha_id(2)
2_B
Anyways I have that logic all figured out. All i need to do is figure out how to pass to that function the id attribute of the current instantiation of my Sample model.
Obviously my problem here is that I am not referring to an instantiation of the class Sample, so the use of "self.id" is causing an error. To be specific:
alpha_id = get_alpha_id(self.id)
NameError: name 'self' is not defined
I have a feeling the solution involves something to do with defining an __init__method but I am not quite sure how I would go about doing it. I have looked at the Model.py base class and I couldn't quite find where the id attribute is defined.
To sum it up, how can I access the current id of an instantiated django model so that I can use that integer value to inform the creation of another attribute?
Instead of making alpha_id a class attribute, you need to make it an instance attribute using the #property decorator on an instance method:
class Sample(models.Model):
sample_name = models.CharField(max_length=30)
entry_date = models.DateField(auto_now_add=True)
#property
def alpha_id(self):
return get_alpha_id(self.id)
def __unicode__(self):
return self.alpha_id

Callable not defined for django.db.models field default

I am using PyCharm 4.5.2, Django 1.8.2.
If I define a class as:
class User(models.Model):
first_name = models.CharField(max_length=256)
last_name = models.CharField(max_length=256)
slug = models.SlugField(max_length=256, unique=True, default=make_slug)
def make_slug(self):
return self.first_name + self.last_name[0]
The IDE highlights default=make_slug with make_slug being undefined. The interpretter agrees and when the development server tries to refresh it exits with status 1 and the error NameError: name 'make_slug' is not defined.
Because it's just the name of a callable, I can't pass arguments. So if I define the function outside the class (to move into a higher scope and be defined) I can't use the class properties. I have read some suggestions that use lambdas but from the Django documentation that is wrong:
Note that lambdas cannot be used for field options like default
because they cannot be serialized by migrations. See that
documentation for other caveats.
What is the proper way to define a callable for default values in a model.
You shouldn't use this method to set your default value, rather than override the save method of the model and use it there. For example:
class User(models.Model):
first_name = models.CharField(max_length=256)
last_name = models.CharField(max_length=256)
slug = models.SlugField(max_length=256, unique=True, default=uuid.uuid1)
def make_slug(self):
return self.first_name + self.last_name[0]
def save(self, *args, **kwargs):
self.slug = self.make_slug()
super().save(*args, **kwargs)
You get this error
NameError: name 'make_slug' is not defined.
because you refer to make_slug before you defined it. If you moved the make_slug function above the slug field, then you wouldn't get that error.
However, it isn't possible to pass any arguments to the callable that you use as the default, so that won't work either. You can't get around that restriction by using a model method as you are trying.
If you need access to the model instance to calculate the default, then setting the value in the save() method as ruddra suggests is a good idea. Note that you might want to check whether or not the model has a primary key, so that you only create the slug when you first create the instance.

Flask-SQLAlchemy Constructor Confusion

Once again this code is from Miguel Grindberg's book "Flask Web Development". In models.py we have 3 classes, a Role class which has 3 roles (User, Moderator, Administrator), a User class (id, username, email, role_id, password_hash, confirmed), and a Permsission class (code below). In chp 9 page 114, he adds some code to the User class to check if the email address belongs to the admin and if so adds it to the role. If not, the user is added to the default role (user). . .
def __init__(self, **kwargs):
super(User, self).__init__(**kwargs)
if self.role is None:
if self.email == current_app.config['FLASKY_ADMIN']:
self.role = Role.query.filter_by(permissions=0xff).first()
if self.role is None:
self.role = Role.query.filter_by(default=True).first()
My question is, why do we need a constructor for this code? A constructor isn't used in any other part of the file (full code below), so why do we need one now? I've looked at this question on Stack (Flask-SQLAlchemy Constructor) which shed some light on the subject as far as the base class constructor, but NOT why I need a constructor at all for this piece of code. .Again, THANKS for any help.
class Permission:
FOLLOW = 0x01
COMMENT = 0x02
WRITE_ARTICLES = 0x04
MODERATE_COMMENTS = 0x08
ADMINISTER = 0x80
class Role(db.Model):
__tablename__ = 'roles'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True)
default = db.Column(db.Boolean, default=False, index=True)
permissions = db.Column(db.Integer)
users = db.relationship('User', backref='role', lazy='dynamic')
#staticmethod
def insert_roles():
roles = {
'User': (Permission.FOLLOW |
Permission.COMMENT |
Permission.WRITE_ARTICLES, True),
'Moderator': (Permission.FOLLOW |
Permission.COMMENT |
Permission.WRITE_ARTICLES |
Permission.MODERATE_COMMENTS, False),
'Administrator': (0xff, False)
}
for r in roles:
role = Role.query.filter_by(name=r).first()
if role is None:
role = Role(name=r)
role.permissions = roles[r][0]
role.default = roles[r][1]
db.session.add(role)
db.session.commit()
def __repr__(self):
return '<Role %r>' % self.name
class User(UserMixin, db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(64), unique=True, index=True)
username = db.Column(db.String(64), unique=True, index=True)
role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
password_hash = db.Column(db.String(128))
confirmed = db.Column(db.Boolean, default=False)
def __init__(self, **kwargs):
super(User, self).__init__(**kwargs)
if self.role is None:
if self.email == current_app.config['FLASKY_ADMIN']:
self.role = Role.query.filter_by(permissions=0xff).first()
if self.role is None:
self.role = Role.query.filter_by(default=True).first()
In typical OO lingo a closer "constructor" equivalent in Python would be a __new__ method rather than __init__. Although to be frank that does not map very well to Python concepts really: you have __new__ method for object creation and __init__ is typically used for initialization. So maybe this is a "split" constructor?
Anyway, .__init__ is where in Python we typically adjust instance's attributes, so super() method above does initialization for User superclass and remainder of child class's __init__ does initialization for the child class. You have to do things like say implementing some logic that depends keyword attributes, etc, somewhere - either you do it outside the class (which violates principle of encapsulation) or you do it in some method inside a class or instance. __init__ is typical and I'm rather astonished thinking why someone would NOT use it.
You also list some SQLAlchemy object with things like declarative __tablename__ class attribute that do not use __init__. Well, zzzeek is a black magician doing wicked and morally controversial stuff so he has risen above peon's __init__ methods that us mere mortals use every day. ;-)
def __init__(self, **kwargs):
super(User, self).__init__(**kwargs)
if self.role is None:
if self.email == current_app.config['FLASKY_ADMIN']:
self.role = Role.query.filter_by(permissions=0xff).first()
if self.role is None:
self.role = Role.query.filter_by(default=True).first()
I think there have some kind of preprocessing and that superclassing is for ealrier checking. So everytime when we querying a User this check will happen.
Whenever a new User instance is created, the constructor assigns a role to that User instance. If this constructor wasn't created, then every time a User instance was created, you would have to assign it a role somewhere else (such as in a route). But that assumes that User instances will be created only within your app if a form is submitted and/or a route is called. This can be messy and leaves holes for some Users to have a None role, which if your app isn't expecting could let users perform actions on the site they are not allowed to do.
By default Flask-SQLAlchemy uses SQLAlchemy's base class defines the constructor (per Flask-SQLAlchemy Constructor). This is why you can create models in Flask-SQLAlchemy without ever having to create a constructor, because it is created by default.
So if you wanted to do something specific in the creation of a model instance (such as assign a value to a column based on some logic), then you need to create the constructor yourself but can use super(MODEL, self).__init__(**kwargs) to save you time in typing out the N columns in that model, since super is inheriting the base class.

Refering to self instance from within a model class

I have a model class, and I want to pass the instance to a method from within this class model:
class myModel(models.Model):
id = models.AutoField()
...several fields...
user= models.CharField(max_length=50, db_column='user', editable=False) # Field name made lowercase.
myFile = models.FileField(max_length=256, blank=True, upload_to=create_file_name(instance, myString))
If I want to define a method create_file_name with one of the params the model instance itself, I don't know how to refer to it.
I know that if I just call upload_to=create_file_name with no params, the method takes automatically instance and filename, but I want to pass the instance and a specific string (not the filename).
Thanks.
Try this instead:
upload_to = lambda instance, filename : create_file(instance, 'whatever')

Categories

Resources