I want refactor some of my code in models because it's a little mess. I have couple models.
class Part(models.Model):
class Category(models.Model):
class Labor(models.Model):
And so on, seven in total. I am generating for them ID. For Part it is:
def save(self, *args, **kwargs):
if not Part.objects.count():
latest = 'XXX00000'
else:
latest = Part.objects.all().order_by('-par_id')[0].par_id
self.par_id = "PAR" + str(int(latest[3:]) + 1).zfill(5)
super(Part, self).save(*args, **kwargs)
And it's pretty similar for rest of classes. Only name of class is changing, three letters identification and paramtere in order_by. I was wondering how can I do it DRY. Because it's 7 lines of code on each class that should be somehow shortened.
I was wondering maybe create BaseModel class inherited from it and somehow change only mentioned things. I would like to get some directions how can I do it better.
Edit:
class Part(models.Model):
par_id = models.CharField(primary_key=True, unique=True, max_length=9, blank=False)
par_name = models.CharField(max_length=50)
def save(self, *args, **kwargs):
if not Part.objects.count():
latest = 'XXX00000'
else:
latest = Part.objects.all().order_by('-par_id')[0].par_id
self.par_id = "PAR" + str(int(latest[3:]) + 1).zfill(5)
super(Part, self).save(*args, **kwargs)
class Category(models.Model):
cat_id = models.CharField(primary_key=True, unique=True, max_length=9)
cat_name = models.CharField(max_length=50)
def save(self, *args, **kwargs):
if not Category.objects.count():
latest = 'XXX00000'
else:
latest = Category.objects.all().order_by('-cat_id')[0].cat_id
self.cat_id = "CAT" + str(int(latest[3:]) + 1).zfill(5)
super(Category, self).save(*args, **kwargs)
That are two o my classes.
Inheriting is definitely a good idea.
You're not giving much information about the models. So there are 2 main options for inheriting models:
A) To use an AbstractModel which would hold the common fields and some common methods. And then use child models to extend the fields and methods as you need. Here is an example from the django docs:
from django.db import models
class CommonInfo(models.Model):
name = models.CharField(max_length=100)
age = models.PositiveIntegerField()
class Meta:
abstract = True
class Student(CommonInfo):
home_group = models.CharField(max_length=5)
B) If you're only interested in inheriting or extending the behavioural parts of your models (like the different methods for generating the id's), a proxy model would be a better option. Take a look at the docs: https://docs.djangoproject.com/en/dev/topics/db/models/#proxy-models
Here is an example taken from the django docs:
from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
class MyPerson(Person):
class Meta:
proxy = True
def do_something(self):
# ...
pass
create class BaseModel(models.Model): and copypaste your save method there, but replace Part with self.__class__ , for example
class BaseModel(models.Model):
# some fields here
class Meta:
abstract = True
def save(self, *args, **kwargs):
first_declared_field = self.__class__._meta.fields[1].name
if self.__class__.objects.count():
latest = getattr(self.__class__.objects.order_by('-' + first_declared_field)[0], first_declared_field)
else:
latest = 'XXX00000'
field_value = first_declared_field.name.split('_')[0].upper() + str(int(latest[3:]) + 1).zfill(5)
setattr(self, first_declared_field, field_value)
super(BaseModel, self).save(*args, **kwargs)
class SomeChildModel(BaseModel):
pass
Related
I create base model and inherit that in all of my models. This is my BaseModel:
class BaseModel(models.Model):
create_date = models.DateTimeField(auto_now_add=True)
update_date = models.DateTimeField(auto_now=True)
created_by = models.ForeignKey('UserManager.User', default=1, on_delete=models.SET_DEFAULT,related_name='created_%(class)ss')
updated_by = models.ForeignKey('UserManager.User', default=1, on_delete=models.SET_DEFAULT,related_name='updated_%(class)ss')
class Meta:
abstract = True
ordering = ['create_date']
def save(self, *args, **kwargs):
self.user = kwargs.pop('user', None)
if self.user:
if self.user.pk is None:
self.created_by = self.user
self.updated_by = self.user
super(BaseModel, self).save(*args, **kwargs)
Now, I want to add some operations to save method of one of child models like this:
class Child(BaseModel):
# Some fields go here.
def save(self, *args, **kwargs):
# Some operations must run here.
But save method of child model does n't run anymore!
How can I use save method of child model with save method of abastract=True model?
If you inherit ChildModel from BaseModel, when you get to the save method in BaseModel 'self.class' is still ChildModel. So it finds the super of Child, which is BaseModel, so calls the save in BaseModel.
So just call ,
super(ChildModel, self).save(*args, **kwargs)
Let's say I have 3 models in Django: Person, Workfield and SubWorkfield.
A person can have many workfield-s and many subWorkfield-s as well, but the subWorkfield-s must be related to their parent workfield-s ( which the person must be related to).
So how do I enforce that whenever a person is related to a subWorkfield then he must also be related to that subWorkfield's parent workfield?
Here's what I have so far, but I don't think it enforces the relationship:
class Person(models.Model):
name = models.CharField(max_length=200)
workfield = models.ManyToManyField(Workfield)
subworkfield = models.ManyToManyField(SubWorkfield)
class Workfield(models.Model):
name = models.CharField(max_length=200)
class SubWorkfield(models.Model):
name = models.CharField(max_length=200)
workfield = models.ForeignKey(Workfield)
I need to have the workfields and subWorkfield-s decoupled because a person can belong to a workfield without any subWorkfield-s.
There really isn't any good way to do what you want purely with the table structure itself. However, if you do the following:
class Person(models.Model):
name = models.CharField(max_length=200)
workfields = models.ManyToManyField(Workfield)
subworkfields = models.ManyToManyField(SubWorkfield)
def add_subworkfield(self, subworkfield):
if subworkfield.workfield not in self.workfields:
return False
else:
self.subworkfields.append(subworkfield)
class Workfield(models.Model):
name = models.CharField(max_length=200)
class SubWorkfield(models.Model):
name = models.CharField(max_length=200)
workfield = models.ForeignKey(Workfield)
And then you will just use the add_subworkfield method when adding subworkfields to Persons.
Alternatively, inside of the Person model you could override the save method:
class Person(models.Model):
...
def save(self, *args, **kwargs):
for subworkfield in self.subworkfields:
if subworkfield.workfield not in self.workfields:
return
super(Person, self).save(*args, **kwargs)
If it's a small app and it's just you, the first way should suffice. However, if you're working on something larger with multiple people, overriding save would be a safety incase someone doesn't use the add_subworkfield method.
There are two models:
class BaseImage(models.Model):
description = models.CharField(max_length=200)
image = models.ImageField(upload_to='images')
class Meta:
abstract = True
class PostImage(BaseImage):
in_text = models.BooleanField()
def __init__(self, *args, **kwargs):
super(BaseImage, self).__init__(*args, **kwargs)
self.image.upload_to = 'images/news/%Y/%m/%d'
How can I set upload_to property in the base model? This my attempt doesn't work:
self.image.upload_to = 'images/news/%Y/%m/%d'
What I can suggest is to write function to get upload to method from instance e.g.
in models.py
#default method to get file upload path
def get_upload_to(instance, filename):
return instance.get_upload_to_path(filename)
class BaseImage(models.Model):
description = models.CharField(max_length=200)
image = models.ImageField(upload_to=get_upload_to)
class Meta:
abstract = True
#method on the class to provide upload path for files specific to these objects
def get_upload_to_path(instance, filename):
return 'images/'+filename
class PostImage(BaseImage):
in_text = models.BooleanField()
#method to provide upload path for PostImage objects
def get_upload_to_path(instance, filename):
#change date.year etc to appropriate variables
return 'images/news/%Y/%m/%d' % (date.year, date.month, date.day)
edit: I completely rewrote the question as the original one didn't clearly explain my question
I want to run a function which is specific to each particular model instance.
Ideally I want something like this:
class MyModel(models.Model):
data = models.CharField(max_length=100)
perform_unique_action = models.FunctionField() #stores a function specific to this instance
x = MyModel(data='originalx', perform_unique_action=func_for_x)
x.perform_unique_action() #will do whatever is specified for instance x
y = MyModel(data='originaly', perform_unique_action=func_for_y)
y.perform_unique_action() #will do whatever is specified for instance y
However there is no datatype FunctionField. Normally this would be solvable with inheritance, and creating subclasses of MyModel, maybe like this:
class MyModel(models.Model):
data = models.CharField(max_length=100)
perform_unique_action = default_function
class MyModelX(MyModel):
perform_unique_action = function_X
class MyModelY(MyModel):
perform_unique_action = function_Y
x = MyModelX(data='originalx')
x.perform_unique_action() #will do whatever is specified for instance x
y = MyModelY(data='originaly')
y.perform_unique_action() #will do whatever is specified for instance y
Unfortunately, I don't think I can use inheritance because I am trying to access the function this way:
class MyModel(models.Model):
data = models.CharField(max_length=100)
perform_unique_action = default_function
class SecondModel(models.Model):
other_data = models.IntegerField()
mymodel = models.ForeignKey(MyModel)
secondmodel = SecondModel.objects.get(other_data=3)
secondmodel.mymodel.perform_unique_action()
The problem seems to be that I don't know what type the foreign key is going to be in SecondModel if I override the perform_unique_action in subclasses.
Can I access MyModel from SecondModel as a foreign key and still have a unique function for each instance of MyModel?
This works for me. I haven't tested it, but you should be able to create another class and override their methods and it'll work. Check the class Meta line, it'll treat it as an abstract class. Here's an example of my actual classes that I'm working on right now.
EDIT: Added VoteComment class and tested it. It works as expected!
class Vote(models.Model):
VOTE_ENUM = (
(VoteEnum.DOWN_VOTE, VoteEnum.toString(VoteEnum.DOWN_VOTE)),
(VoteEnum.NONE, VoteEnum.toString(VoteEnum.NONE)),
(VoteEnum.UP_VOTE, VoteEnum.toString(VoteEnum.UP_VOTE)),
)
question = models.ForeignKey(Question, null=False, editable=False, blank=False)
voter = models.ForeignKey(User, blank=False, null=False, editable=False)
vote_type = models.SmallIntegerField(default=0, null=False, blank=False, choices=VOTE_ENUM)
class Meta:
abstract = True
def is_upvote(self):
return self.vote_type > 0
def is_downvote(self):
return self.vote_type < 0
class VoteAnswer(Vote):
answer = models.ForeignKey(Answer, null=False, editable=False, blank=False)
class Meta:
unique_together = (("voter", "answer"),) # to prevent user from voting on the same question/answer/comment again
def __unicode__(self):
vote_type = "UP" if vote_type > 0 else ("DOWN" if vote_type < 0 else "NONE")
return u"{0}: [{1}] {2}".format(user.username, vote_type, answer.text[:32])
def is_upvote(self):
return "FOO! "+str(super(VoteAnswer, self).is_upvote())
class VoteComment(Vote):
comment = models.ForeignKey(Comment, null=False, editable=False, blank=False)
class Meta:
unique_together = (("voter", "comment"),) # to prevent user from voting on the same question/answer/comment again
def __unicode__(self):
vote_type = "UP" if vote_type > 0 else ("DOWN" if vote_type < 0 else "NONE")
return u"{0}: [{1}] {2}".format(user.username, vote_type, comment.text[:32])
def is_upvote(self):
return "BAR!"
I came up with two ways of having a specific function defined for each object. One was using marshal to create bytecode which can be stored in the database (not a good way), and the other was by storing a reference to the function to be run, as suggested by Randall. Here is my solution using a stored reference:
class MyModel(models.Model):
data = models.CharField(max_length=100)
action_module = models.CharField(max_length=100)
action_function = models.CharField(max_length=100)
class SecondModel(models.Model):
other_data = models.IntegerField()
mymodel = models.ForeignKey(MyModel)
secondmodel_obj = SecondModel.objects.get(other_data=3)
#The goal is to run a function specific to the instance
#of MyModel referred to in secondmodel_obj
module_name = secondmodel_obj.mymodel.action_module
func_name = secondmodel_obj.mymodel.action_function
module = __import__(module_name)
func = vars(module)[func_name]
func()
Thanks to everyone who replied, I couldn't have got to this answer if it weren't for your help.
You could achive some similar behavior overriding the save method. And providing special callbacks to your instances.
Something like:
def default_function(instance):
#do something with the model instance
class ParentModel(model.Model):
data = models.CharField()
callback_function = default_function
def save(self, *args, **kwargs):
if hasattr(self, 'callback_function'):
self.callback_function(self)
super(ParentModel, self).save(*args, **kwargs)
class ChildModel():
different_data = models.CharField()
callback_function = other_fun_specific_to_this_model
instance = ChildModel()
#Specific function to this particular instance
instance.callback_function = lambda inst: print inst.different_data
instance.save()
You can write endpoints on your server and limit their access to just your self. Then store in each model instance corresponding url. For example:
views.py
def funx_x(request):
pass
def func_y(request):
pass
models.py:
class MyModel(models.Model):
data = models.CharField(max_length=100)
perform_unique_action = models.URLField()
and then:
x = MyModel(data='originalx', perform_unique_action='http://localhost/funx_x')
requests.post(x.perform_unique_action)
i dont know whether i understand u correct or not. but you can check out this example here.
Example:
A string representing an attribute on the model. This behaves almost the same as the callable, but self in this context is the model instance. Here's a full model example:
class Person(models.Model):
name = models.CharField(max_length=50)
birthday = models.DateField()
def decade_born_in(self):
return self.birthday.strftime('%Y')[:3] + "0's"
decade_born_in.short_description = 'Birth decade'
class PersonAdmin(admin.ModelAdmin):
list_display = ('name', 'decade_born_in')
I am trying to use the ModelForm to add my data. It is working well, except that the ForeignKey dropdown list is showing all values and I only want it to display the values that a pertinent for the logged in user.
Here is my model for ExcludedDate, the record I want to add:
class ExcludedDate(models.Model):
date = models.DateTimeField()
reason = models.CharField(max_length=50)
user = models.ForeignKey(User)
category = models.ForeignKey(Category)
recurring = models.ForeignKey(RecurringExclusion)
def __unicode__(self):
return self.reason
Here is the model for the category, which is the table containing the relationship that I'd like to limit by user:
class Category(models.Model):
name = models.CharField(max_length=50)
user = models.ForeignKey(User, unique=False)
def __unicode__(self):
return self.name
And finally, the form code:
class ExcludedDateForm(ModelForm):
class Meta:
model = models.ExcludedDate
exclude = ('user', 'recurring',)
How do I get the form to display only the subset of categories where category.user equals the logged in user?
You can customize your form in init
class ExcludedDateForm(ModelForm):
class Meta:
model = models.ExcludedDate
exclude = ('user', 'recurring',)
def __init__(self, user=None, **kwargs):
super(ExcludedDateForm, self).__init__(**kwargs)
if user:
self.fields['category'].queryset = models.Category.objects.filter(user=user)
And in views, when constructing your form, besides the standard form params, you'll specify also the current user:
form = ExcludedDateForm(user=request.user)
Here example:
models.py
class someData(models.Model):
name = models.CharField(max_length=100,verbose_name="some value")
class testKey(models.Model):
name = models.CharField(max_length=100,verbose_name="some value")
tst = models.ForeignKey(someData)
class testForm(forms.ModelForm):
class Meta:
model = testKey
views.py
...
....
....
mform = testForm()
mform.fields["tst"] = models.forms.ModelMultipleChoiceField(queryset=someData.objects.filter(name__icontains="1"))
...
...
Or u can try something like this:
class testForm(forms.ModelForm):
class Meta:
model = testKey
def __init__(self,*args,**kwargs):
super (testForm,self ).__init__(*args,**kwargs)
self.fields['tst'].queryset = someData.objects.filter(name__icontains="1")
I know this is old; but its one of the first Google search results so I thought I would add how I found to do it.
class CustomModelFilter(forms.ModelChoiceField):
def label_from_instance(self, obj):
return "%s %s" % (obj.column1, obj.column2)
class CustomForm(ModelForm):
model_to_filter = CustomModelFilter(queryset=CustomModel.objects.filter(active=1))
class Meta:
model = CustomModel
fields = ['model_to_filter', 'field1', 'field2']
Where 'model_to_filter' is a ForiegnKey of the "CustomModel" model
Why I like this method:
in the "CustomModelFilter" you can also change the default way that the Model object is displayed in the ChoiceField that is created, as I've done above.
is the best answer:
BookDemoForm.base_fields['location'] = forms.ModelChoiceField(widget=forms.Select(attrs={'class': 'form-control select2'}),queryset=Location.objects.filter(location_for__fuel=True))