I have some code in which I have defined some Python models.
For example:
class Store(object):
'''
The store object... Should it be a Django model?
'''
def __init__(self, *args, **kwargs):
self.name = kwargs['name']
self.id = kwargs['id']
def __str__(self, *args, **kwargs):
return self.name
kittenstore = Store(name='kittenstore', id=1)
dogstore = Store(name='dogstore', id=2)
stores_list = [kittenstore, dogstore]
class Stores(object):
def __init__(self, *args, **kwargs):
self.stores = stores_list
def get_choices(self):
store_choices = ()
for store in self.stores:
store_choices+= ((store.id, store.name),)
return store_choices
I'd like to refer to those stores from another Django model.
I could use a models.Integerfield with choices=get_choices(), but when I filter in a related models, then I always get the id back and I need to find the correlating store if I want to use it in my code.
Is there not a (python) trick to get back the Store object instead of the id?
Maybe with a custom Field?
"Referring to these stores from another model" implies that there should be some kind of (logical) relationship between those models. This is usually depicted by connecting the models with a foreign key.
In other words, you can try something like this:
from django.db import models
class Store(models.Model):
name = models.CharField(max_length=50)
id = models.CharField(max_length=5)
def __str__(self):
return 'Store is named: %s' % self.name
class Stores(models.Model):
store = models.ForeignKey(Store, on_delete=models.CASCADE)
def __str__(self):
return "Stores is at %s store" % (self.store)
Create your store and save it:
kittenstore = Store(name='kittenstore', id='1')
kittenstore.save()
Instantiate your Stores:
st = Stores(id=None, store=kittenstore)
st.save()
And now you can access your store object like this:
st.kittenstore
The relationship I used in this example is a Many-to-one relationship. You should decide on the relationship that best suits your needs by taking a look at the Django documentation regarding model relationship.
***EDIT***If you mean associate them via the model's attributes then you can assign a model attribute to an instance of a class. For a related example on the matter, take a look at this.
Related
I am try to refer 'spot_price' of model 'Spot' in model 'Basis' in django model layer, How can I manage this?
I have designed view.py to automaticaly render the templates. so I am not able to modifty any view.py to choose data like 'models.B.objects.get().field'.
and more, str is set to indicate the date, so, in the django backstage admin, the 'spot' field display would be 'date' formate, how would be change to 'spot_price'?
model Spot
class Spot(models.Model):
date = models.DateField(primary_key=True)
spot_price = models.FloatField(blank=True, null=True)
def __str__(self):
return str(self.date) if self.date else ''
need to refer the model Spot'spot_price by date, cause date is unique but spot_price is not
class Basis(models.Model):
date = models.DateField(primary_key=True)
major_future_contract_close_price = models.FloatField(blank=True)
spot = models.OneToOneField(Spot, on_delete=models.CASCADE)
basis = models.FloatField(default=calculate_basis)
def __str__(self):
return str(self.date) if self.date else ''
def calculate_basis(self):
return abs(self.major_future_contract_close_price -
self.spot.spot_price)
I expect the Basis.query.data would to like 'date: 2019-04-25, major_future_contract_close_price: 100.0, spot: 96.5, basis: 3.5'
You can't use class method as default, because it requires self, which is not existing when you are still creating the object.
If you need to have it stored in field (database), override default save() method or use signals to modify the basis field once your object is created. Also note that you have to recalculate basis every time close_price or spot_price changes, as the value is just written in database.
Probably better solution would be to use #property so for anyone that will be using you model it will look like a field, but it will dynamically calculate value based on current data.
For example, I'm overriding save() to calculate the basis field. I set it as editable=False, which means it won't appear in forms by default (and you will not be able to see it in admin!). You can safely remove that part if you want to just look at the values.
Additionally I add basis_as_property property.
class Basis(models.Model):
date = models.DateField(primary_key=True)
major_future_contract_close_price = models.FloatField(blank=True)
spot = models.OneToOneField(Spot, on_delete=models.CASCADE)
basis = models.FloatField(editable=False, blank=True)
#property
def basis_as_property(self):
return '%s' % (self.calculate_basis())
def __str__(self):
return str(self.date) if self.date else ''
def save(self, *args, **kwargs):
if not self.basis:
self.basis = self.calculate_basis()
super(Basis, self).save(*args, **kwargs)
def calculate_basis(self):
return abs(self.major_future_contract_close_price - self.spot.spot_price)
As for Spot str repr, I don't think it's possible to change it based on where it is referenced. If you want to use spot_price, you can just use: return str(self.spot_price) if self.spot_price else str(self.date)
I've written a custom editable table with columns subclassing from django_tables2.Column, but keep struggling with rendering a select tag in my custom column. Considering the model:
myapp/models.py
from django.db import models
from myapp.utils.enums import MyModelChoices
class MyModel(models.Model):
bound_model = models.ForeignKey(
SomeOtherModel,
related_name='bound_model'
)
used_as = models.CharField(
max_length=50,
blank=True,
null=True,
choices=MyModelChoices.choices()
)
and my enum in myapp/utils/enums.py:
class MyModelChoices:
__metaclass__ = EnumMeta # Logic irrelevant
First = 'First',
Second = 'Second',
Third = 'Third'
I end up with custom column like this:
import django_tables2 as tables
from django.forms import ChoiceField
class ChoicesColumn(tables.Column):
def __init__(self, choices, attrs=None, **extra):
self.choices = choices
kwargs = {'orderable': False, 'attrs': attrs}
kwargs.update(extra)
super(ChoicesColumn, self).__init__(**kwargs)
def render(self, value, bound_column):
select = ChoiceField(choices=self.choices)
return select.widget.render(
bound_column.name,
self.label_to_value(value)
)
def label_to_value(self, label):
for (v, l) in self.choices:
if l == label:
return v
which is later called in my table class like this:
import django_tables2 as tables
from myapp.models import MyModel
from myapp.tables.utils import ChoicesColumn
class MyTable(tables.Table):
name = tables.Column()
used_as = ChoicesColumn(
choices=lambda record: record.used_as.choices()
)
def render_name(self, record):
return record.bound_model.name
class Meta:
model = MyModel
fields = ('name', 'used_as',)
but still there's rendered just a plain <td></td> with text instead of select field. What am I doing wrong in this situation? I'm using Python 2.7, Django 1.8 and django-tables2 1.16.0. Thanks in advance for your advice!
UPDATE
I changed my custom column class like this:
class ChoicesColumn(tables.Column):
def __init__(self, attrs=None, **extra):
kwargs = {'orderable': False, 'attrs': attrs}
kwargs.update(extra)
super(ChoicesColumn, self).__init__(**kwargs)
def render(self, value, bound_column):
options = [self.render_option(c) for c in value]
html_template = '''
<select name={}>{}</select>
'''.format(bound_column.name, options)
return mark_safe(html_template)
def render_option(self, choice):
return '<option value={0}>{0}</option>'.format(choice)
and added a render_options method according to this paragraph in documentation:
class MyTable(tables.Table):
name = tables.Column(accessor='pk')
# With or without accessor it doesn't work neither way
used_as = ChoicesColumn(accessor='used_as')
def render_name(self, record):
return record.bound_model.name
def render_used_as(self, record):
return record.used_as.choices()
class Meta:
model = MyModel,
fields = ('name', 'options',)
but this method isn't even executed on render, what I've spotted while debugging, though the method before it executes when I reload the page and renders data correctly. Is that because name column uses the library class, and options column uses custom class inherited from it? If so, what is my subclass missing?
ANOTHER UPDATE
I figured out what was the previous problem with choices, though it didn't solve the problem :( The thing was that I was passing model instance's field used_as, which was set to None, thus it would never populate the ChoiceField. So, I rolled back my custom column class to the initial variant, and in my table class instead of
used_as = ChoicesColumn(
choices=lambda record: record.used_as.choices()
)
I imported MyModelChoices enum and used it instead of model instance
used_as = ChoicesColumn(choices=MyModelChoices.choices())
and now I see the options passing to constructor, though the render method isn't still called for some mysterious reason =/
LAST UPDATE AS FOR NOW
As for the current moment my custom column and table look like this:
class ChoicesColumn(tables.Column):
def __init__(self, choices, attrs=None, **extra)
self.choices = choices
self.choices.insert(0, ('', '------'))
kwargs = {'orderable': False, 'attrs': attrs}
kwargs.update(extra)
super(ChoicesColumn, self).__init__(**kwargs)
def render(self, value, bound_column):
select = forms.ChoiceField(choices=self.choices)
return select.widget.render(bound_column.name, value)
class MyTable(tables.Table):
name = tables.Column(accessor='pk')
used_as = ChoiceColumn(UsedAs.choices(), accessor='used_as')
def render_name(self, record):
return record.bound_model.name
def render_used_as(self, record):
if record.used_as is None:
return ''
return record.used_as
class Meta:
model = MyModel
fields = ('name', 'used_as')
The ChoiceColumn render method and the corresponding method in table class are never called on rendering stage (unlike the other columns), and I completely give up. Please, be merciful enough either to shoot me or tell me where exactly I'm an idiot :)
So, as I accidentally found out, the problem was in accessor attribute – when changed from
used_as = ChoiceColumn(UsedAs.choices(), accessor='used_as')
to
used_as = ChoiceColumn(UsedAs.choices(), accessor='pk')
it finally rendered. I don't understand why that happened and would be very grateful if someone explained that to me.
There is an easier way:
If you have a Enum column (say used_as), you can change the renderer so that it displays the value (instead of the name). Place this in the Table definition (in class MyTable(tables.Table) ).
def render_used_as(self,value):
v = value.split(".")[1]
members = MyModelChoices.__members__
return (members[v].value)
Note that I was using a bit of a different syntax for the Enum
from enum import Enum
Class MyModelChoices(Enum):
First = 'First'
Second = 'Second'
Third = 'Third'
Note: render_used_as is render_%s with %s = variable name
I feel like this must be really simple, but after a couple of days of trying I'm officially clueless.
I have a dictionary where the keys are objects and the values are lists of objects. Here's how I want to use that info to construct a form:
for object in dictionary:
name_of_field = object.slug
name_of_field = forms.ModelMultipleChoiceField(widgets=forms.CheckboxSelectMultiple, queryset=dictionary[object])
Of course, just putting name_of_field in there twice doesn't work to generate dynamically named fields. What this actually does is create a single field called "name_of_field" using the final object it iterates over. I wish it would create a field for every key in the dictionary, named using the key object's slug and with a choice set of that key's values.
Is there a way to loop through this dictionary and create the form fields I want? I feel like the answer lies in superclassing __init__, but I still can't wrap my head around how to get multiple fields with different names.
You don't say where you are using this code. You should be putting it into the form's __init__ method, from where you can reference self.fields:
class DynamicForm(forms.Form):
def __init__(self, *args, **kwargs):
dynamic_fields = kwargs.pop('dynamic_fields')
super(DynamicForm, self).__init__(*args, **kwargs)
for key, value in dynamic_fields:
self.fields[key.slug] = forms.ModelMultipleChoiceField(widget=forms.CheckboxSelectMultiple, queryset=value)
I feel like I want to contribute to this question although it is really old, because I could not solve my question with the answer alone.
For a given model with the form:
class Product(models.Model):
data = models.JSONField()
store = models.ForeignKey(Store, on_delete = models.CASCADE)
number = models.PositiveIntegerField()
a dynamically created form can be created (careful, this is a simplified case, where all products of the same store share the same keys in the JSONField):
class ProductForm(forms.Form):
def __init__(self, first_product, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["number"] = forms.IntegerField(required = True)
for key in first_product.data.keys():
self.fields[key] = forms.CharField(required = False)
class Meta:
model = Product
fields = ["number", "data"]
The form must be called by handing it the first_product variable in the view:
class SomeView(TemplateView):
template_name = "appName/sometemplatename.html"
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(**kwargs)
store = Store.objects.get(user = self.request.user)
tmp = Product.objects.filter(store = store).first()
context["testform"] = ProductForm(first_product = tmp)
return context
This is a vastly general approach to start from.
I have a model that looks like this and stores data as key-value pairs.
class Setting(models.Model):
company = models.ForeignKey(
Company
)
name = models.CharField(
null=False, max_length=255
)
value= models.CharField(
null=False, max_length=255
)
I have a custom Manager on this Model which overrides the get method. When the queries my Model like Settings.objects.get(company=1), I use my over-ridden get method to execute a self.objects.filter(company=1) which returns a list of objects. Can I generate one single custom QuerySet which has all the key-value pairs as fields.
Example:
If the data in my Model was like this:
company name value
------- ---- -----
1 theme custom
1 mode fast
1 color green
I'd like to return a query set that would be pivoted like so when someone executed Settings.objects.get(company=1):
company theme mode color
------ ----- ---- -----
1 custom fast green
I've tried to be verbose but do let me know if I should explain better. I'm not sure if the Django Models allow this scenario.
Thank you everyone.
Edit: Using Proxy models
Is this something I could accomplish using Proxy Models i.e. having a base model to store the key value fields and custom proxy model with normal get and save method?
Here's how I did it.
I needed to do this because I had a Model that stored information as key value pairs and I needed to build a ModelForm on that Model but the ModelForm should display the key-value pairs as fields i.e. pivot the rows to columns. By default, the get() method of the Model always returns a Model instance of itself and I needed to use a custom Model. Here's what my key-value pair model looked like:
class Setting(models.Model):
domain = models.ForeignKey(Domain)
name = models.CharField(null=False, max_length=255)
value = models.CharField(null=False, max_length=255)
objects = SettingManager()
I built a custom manager on this to override the get() method:
class SettingManager(models.Manager):
def get(self, *args, **kwargs):
from modules.customer.proxies import *
from modules.customer.models import *
object = type('DomainSettings', (SettingProxy,), {'__module__' : 'modules.customer'})()
for pair in self.filter(*args, **kwargs): setattr(object, pair.name, pair.value)
setattr(object, 'domain', Domain.objects.get(id=int(kwargs['domain__exact'])))
return object
This Manager would instantiate an instance of this abstract model. (Abstract models don't have tables so Django doesn't throw up errors)
class SettingProxy(models.Model):
domain = models.ForeignKey(Domain, null=False, verbose_name="Domain")
theme = models.CharField(null=False, default='mytheme', max_length=16)
message = models.CharField(null=False, default='Waddup', max_length=64)
class Meta:
abstract = True
def __init__(self, *args, **kwargs):
super(SettingProxy, self).__init__(*args, **kwargs)
for field in self._meta.fields:
if isinstance(field, models.AutoField):
del field
def save(self, *args, **kwargs):
with transaction.commit_on_success():
Setting.objects.filter(domain=self.domain).delete()
for field in self._meta.fields:
if isinstance(field, models.ForeignKey) or isinstance(field, models.AutoField):
continue
else:
print field.name + ': ' + field.value_to_string(self)
Setting.objects.create(domain=self.domain,
name=field.name, value=field.value_to_string(self)
)
This proxy has all the fields that I'd like display in my ModelFom and store as key-value pairs in my model. Now if I ever needed to add more fields, I could simply modify this abstract model and not have to edit the actual model itself. Now that I have a model, I can simply build a ModelForm on it like so:
class SettingsForm(forms.ModelForm):
class Meta:
model = SettingProxy
exclude = ('domain',)
def save(self, domain, *args, **kwargs):
print self.cleaned_data
commit = kwargs.get('commit', True)
kwargs['commit'] = False
setting = super(SettingsForm, self).save(*args, **kwargs)
setting.domain = domain
if commit:
setting.save()
return setting
I hope this helps. It required a lot of digging through the API docs to figure this out.
Can I read polymorphic models from a single database table, with their behaviour depending on a (boolean) field of the model?
In one of my models the behaviour is slightly different if the instance is 'forward' vs. 'backward' or 'left' vs. 'right'. That leads to a lot of if-clauses and code duplication. So I want to have a Forward- and a Backward-variant of the model that encapsulate the different behaviours.
But how can I make the models manager return the instances of the right classes? Do I have to overwrite __init__ of the model?
Maybe it's easier to explain with an example. What I'm doing:
class Foo(models.Model):
forward = models.BooleanField()
other_fields = ...
def do_foobar(bar):
if self.forward:
gap = bar.end_pos - bar.current_pos
self.do_forward_move(max = gap)
if self.pos==bar.end_pos:
and so on ...
else:
gap = bar.current_pos - bar.start_pos
self.do_backward_move(max = gap)
if self.pos==bar.start_pos:
and so on ...
What I want to do:
class Foo(models.Model):
forward = models.BooleanField()
other_fields = ...
def __init__(*args, **kwargs):
""" return ForwardFoo or BackwardFoo
depending on the value of 'forward'"""
How?
def do_foobar(bar):
gap = self.calculate_gap(bar)
self.do_move(max = gap)
if self.end_point_reached():
and so on ...
class ForwardFoo(Foo):
def calculate_gap(bar):
return bar.end_pos - bar.current_pos
and so on ...
for f in Foo.objects.all():
f.do_foobar(bar)
Or is there a totally different way to avoid this kind of code duplication?
Proxy models:
class Foo(models.Model):
# all model attributes here
class ForwardFooManager(models.Manager):
def get_query_set(self, *args, **kwargs):
qs = super(ForwardFooManager, self).get_query_set(*args, **kwargs)
return qs.filter(forward=True)
class ForwardFoo(Foo):
class Meta:
proxy = True
objects = ForwardsFooManager()
# methods for forward model
class BackwardFooManager(models.Manager):
def get_query_set(self, *args, **kwargs):
qs = super(BackwardFooManager, self).get_query_set(*args, **kwargs)
return qs.filter(forward=False)
class BackwardFoo(Foo):
class Meta:
proxy = True
objects = BackwardFooManager()
# methods for backward model
The above creates two proxy models: one for forward, one for backward. Proxy models do not have their own database table; they use the same database table as the model they inherit from. (This also means you cannot add any additional fields to the proxy model, only methods.)
There's also a custom manager for to force each one to only return the subset of items that belong to each. Just add whatever specific methods you need and you're done.