I have the following two model class in django.
class Rule(models.Model):
name = models.CharField(max_length=50)
user = models.ForeignKey(User, related_name='rules', null=True, blank=True)
threshold = models.CharField(max_length=50, null=True, blank=True)
alert_value = models.CharField(max_length=50, null=True, blank=True)
is_internal = models.BooleanField(default=False)
def __unicode__(self):
return self.name
def to_json(self):
return {
'name': self.name,
'threshold': self.threshold,
'alert_value': self.alert_value
}
class Module(models.Model):
name = models.CharField(max_length=50)
description = models.TextField(null=True, blank=True)
is_internal = models.BooleanField(default=False)
rules = models.ManyToManyField(Rule)
def to_json(self):
return {
'name': self.name,
'description': self.description,
'rules': [r.to_json() for r in self.rules.all()]
}
def __unicode__(self):
return self.name
Now I have the following code to save a Module object which implicitly contains a rules object in my view.py
def create_module(request):
if request.method == 'POST':
module_name = request.POST.get('name')
module_description = request.POST.get('description')
rule_ids = request.POST.getlist('rule_id')
rules = None
for rule_id in rule_ids:
try:
rules = models.Rule.objects.filter(pk__in=rule_id)
except models.Rule.DoesNotExist:
pass
module = models.Module(name=module_name,
description=module_description,
rules=rules)
module.save()
I get the rules correctly here but when save gets called I get an error
Exception Type: TypeError at /modules/create/
Exception Value: 'rules' is an invalid keyword argument for this function
How to overcome this when I want to save an object graph.
rules is not really a field on the model, it's an entry in a linking table - so it can't be saved until the Module entry exists. Also note that your loop is such that it will never consist of more than one Rules object, because you overwrite the rules variable each time. Instead you should simply get all the Rules and add them in one go.
module = models.Module(name=module_name,
description=module_description)
module.save()
rules = models.Rule.objects.filter(pk__in=rule_ids)
module.rules = rules
There's no need to save again after that: assigning to a related queryset does the database operation automatically. Also note that filter will not raise a DoesNotExist exception: if there is no matching rule, then there simply won't be an element in the resulting queryset.
you are overriding the rules queryset inside try and filter() doesnot raise DoesNotExist exception btw..
try this:
module = models.Module(name=module_name,description=module_description)
module.save()
#first save 'module' and add 'rules' from filter()-result
rules = models.Rule.objects.filter(pk__in=rule_ids)
module.rules = rules
module.save()
more about how to save m2m in django
Related
I have a model built like this
class ApiPartner(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=30, verbose_name=_('Name'))
api_key = models.CharField(max_length=50, verbose_name=_('API key'), null=True)
secret_key = models.CharField(max_length=50, verbose_name=_('Secret key'), null=True)
client_key = models.CharField(max_length=50, verbose_name=_('Client key'), null=True)
endpoint = models.CharField(max_length=50, verbose_name=_('Endpoint'), null=True)
logo = models.ImageField(upload_to='logos/', null=True)
evey API partner has its own method to retrieve data, for example
def get_youtube_posts(endpoint,api_key):
results=list(requests.get(endpoint+'?key='+api_key).json())
return results[0:50]
def get_instagram_posts(endpoint,api_key,secret_key):
return requests.get(endpoint+'?key='+api_key+'&secret='+secret_key)
the question is: how do i assign the 'get_posts' function to the model so i can call a generic ApiPartner.get_posts() and it will retrieve the posts using the given function?
I'm thinking about like a models.FunctionField but i know that doesn't exist.
I think this is more a logical problem than a technical one but i can't find a way. Thank you
Maybe I'm understanding the question wrong; but you can just assign it as a property on the model class:
class MyModel(models.Model):
fields...
#property
def youtube_posts(self):
results=list(requests.get(self.endpoint+'?key='+self.api_key).json())
return results[0:50]
#property
def instagram_posts(self):
return requests.get(self.endpoint+'?key='+self.api_key+'&secret='+self.secret_key)
Then you can call it with the instance of your model.
mymodel = MyModel.objects.all().first()
youtube_posts = mymodel.youtube_posts
# or
instagram_posts = mymodel.instagram_posts
But this will only return one or the other since your models are based on one specific endpoint.
To create a more generic method on the model, use the above methods, plus this:
#property
def platform_posts(self)
if "instagram" in self.endpoint:
return self.instagram_posts
elif "youtube" in self.endpoint:
return self.youtube_posts
... You get the gist.
I got following models:
class OrderItem(models.Model):
ordered_amount = models.IntegerField(validators=[MinValueValidator(0)])
amount = models.IntegerField(default=0)
order = models.ForeignKey(
Order, on_delete=models.CASCADE, related_name="order_items"
)
class Order(models.Model):
reference = models.CharField(max_length=50)
purchase_order = models.CharField(max_length=15, blank=True, null=True)
I'm now writing a serializer for listing orders. In this OrderSerializer I need to access amount and ordered_amount in the OrderItem class. How do I do this?
This is What I have now:
class AdminOrderListSerializer(serializers.ModelSerializer):
amount = serializers.IntegerField()
ordered_amount = serializers.IntegerField()
class Meta:
model = Order
fields = [
"purchase_order",
"reference",
"amount",
"ordered_amount",
]
# noinspection PyMethodMayBeStatic
def validate_amount(self, order):
if order.order_items.amount:
return order.order_items.amount
return
# noinspection PyMethodMayBeStatic
def validate_ordered_amount(self, order):
if order.order_items.ordered_amount:
return order.order_items.ordered_amount
return
This gives me following error:
AttributeError: Got AttributeError when attempting to get a value for field amount on serializer AdminOrderItemListSerializer.
The serializer field might be named incorrectly and not match any attribute or key on the Order instance.
Original exception text was: 'Order' object has no attribute 'amount'.
There are many ways to that, one of them is SerializerMethodField:
from django.db.models import Sum
class AdminOrderListSerializer(serializers.ModelSerializer):
amount = serializers.SerializerMethodField()
ordered_amount = serializers.SerializerMethodField()
def get_amount(self,obj):
return obj.order_items.aggregate(sum=Sum('amount'))['sum']
def get_ordered_amount(self,obj):
return obj.order_items.aggregate(sum=Sum('order_amount'))['sum']
Optimized solution
Another way of achieving this is to annotate the data to queryset, and access them in serializer. For that, you need to change in view:
class SomeView(ListAPIView):
queryset = Order.objects.annotate(amount=Sum('order_items__amount'),order_amount=Sum('order_items__order_amount'))
This is a optimized solution because it reduces database hits(it only hits once).
I want to do a query on the django User table like this:
u = User.objects.filter(member__in = member_list)
where:
class Member(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
dob = models.DateField('Date of Birth', blank=True, null=True)
and member_list is a list of eligible members.
The query works fine but the problem is I do not actually know the model member is called member. It could be called anything.
I store the name of the model I want in a model called Category. I have a link to the name of the model through content_type.Category is defined as:
class Category(models.Model):
name = models.CharField('Category', max_length=30)
content_type = models.ForeignKey(ContentType)
filter_condition = JSONField(default="{}", help_text=_(u"Django ORM compatible lookup kwargs which are used to get the list of objects."))
user_link = models.CharField(_(u"Link to User table"), max_length=64, help_text=_(u"Name of the model field which links to the User table. 'No-link' means this is the User table."), default="No-link")
def clean (self):
if self.user_link == "No-link":
if self.content_type.app_label == "auth" and self.content_type.model == "user":
pass
else:
raise ValidationError(
_("Must specify the field that links to the user table.")
)
else:
if not hasattr(apps.get_model(self.content_type.app_label, self.content_type.model), self.user_link):
raise ValidationError(
_("Must specify the field that links to the user table.")
)
def __unicode__(self):
return self.name
def _get_user_filter (self):
return str(self.content_type.app_label)+'.'+str(self.content_type.model)+'.'+str(self.user_link)+'__in'
def _get_filter(self):
# simplejson likes to put unicode objects as dictionary keys
# but keyword arguments must be str type
fc = {}
for k,v in self.filter_condition.iteritems():
fc.update({str(k): v})
return fc
def object_list(self):
return self.content_type.model_class()._default_manager.filter(**self._get_filter())
def object_count(self):
return self.object_list().count()
class Meta:
verbose_name = _("Category")
verbose_name_plural = _("Categories")
ordering = ('name',)
So I can retrieve the name of the model that links to User but I then need to convert it into a class which I can include in a query.
I can create an object x = category.content_type.model_class() which gives me <class 'cltc.models.Member'> but when I them perform a query s = User.objects.filter(x = c.category.object_list()) I get the error Cannot resolve keyword 'x' into field.
Any thoughts most welcome.
The left hand side of the filter argument is a keyword, not a python object, so x is treated as 'x', and Django expects a field called x.
To get around this, you can ensure that x is a string, and then use the python **kwarg syntax:
s = User.objects.filter(**{x: c.category.object_list()})
Thanks to https://stackoverflow.com/a/4720109/823020 for this.
I'm trying to create a simple action that gets one record (with ManyToMany relationship) from the database then display the JSON serialized instance, here is how I did it for now:
the service model:
class SystemService(models.Model):
name = models.CharField(max_length=35, unique=True, null=False, blank=False)
verion = models.CharField(max_length=35, unique=True, null=False, blank=False)
def __str__(self):
return self.name
the server model:
class Server(models.Model):
name = models.CharField(max_length=35, unique=True, null=False, blank=False)
ip_address = models.GenericIPAddressField(protocol='both', unpack_ipv4=True,
null=False, blank=False, unique=True)
operating_system = models.ForeignKey(OperatingSystem, null=False, blank=False)
monitored_services = models.ManyToManyField(SystemService)
info = models.CharField(max_length=250, null=True, blank=True)
pause_monitoring = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.name
Here is how it is now using muj-gabriel answer:
def get_server(request, server_id):
try:
server_object = Server.objects.get(id=server_id)
data = {
'name': server_object.name,
'ip_address': server_object.ip_address,
'os': server_object.operating_system.name,
'info': server_object.info,
'monitoring_paused': server_object.pause_monitoring,
'created_at': server_object.created_at,
'update_at': server_object.updated_at,
'services': {service['id']: service['name'] for service
in server_object.monitored_services.values('id', 'name')}
}
return JsonResponse(data)
except Server.DoesNotExist:
return JsonResponse({'error': 'Selected object does not exits!'})
I don't think that what I did is good enough since I have to repeat the same thing each time I need to get one instance as JSON, so I would like to know if is there a pythonic and dynamic way to do it?
If you just need the values 'id' and 'name' I suggest using this:
'services': {service['id']: service['name']
for service in server_object.monitored_services.values('id', 'name')}
See django docs
Also you can move the code into a property to the Model class to reuse it elsewhere.
class Server(models.Model):
.......
#property
def data(self):
return {
'name': self.name,
'ip_address': self.ip_address,
'os': self.operating_system.name,
'info': self.info,
'monitoring_paused': self.pause_monitoring,
'created_at': self.created_at,
'update_at': self.updated_at,
'services': {service['id']: service['name'] for service in self.monitored_services.values('id', 'name')}
}
Your view function will be:
def get_server(request, server_id):
try:
server_object = Server.objects.get(id=server_id)
return JsonResponse(server_object.data)
except Server.DoesNotExist:
return JsonResponse({'error': 'Selected object does not exits!'})
After looking a bit in the Django doc I found the model_to_dict function, which basically do what I need (Model instance to Dict), but for the ManyToMany relationship it only returns a list of PKs, so I wrote my own function based on it:
def db_instance2dict(instance):
from django.db.models.fields.related import ManyToManyField
metas = instance._meta
data = {}
for f in chain(metas.concrete_fields, metas.many_to_many):
if isinstance(f, ManyToManyField):
data[str(f.name)] = {tmp_object.pk: db_instance2dict(tmp_object)
for tmp_object in f.value_from_object(instance)}
else:
data[str(f.name)] = str(getattr(instance, f.name, False))
return data
Hope it helps someone else.
I have the following models:
class Product(models.Model):
active = models.BooleanField(default=True)
name = models.CharField(max_length=40, unique=True)
acronym = models.CharField(max_length=3, unique=True)
bool1_name = models.CharField(max_length=60, blank=True, null=True)
bool1_default = models.BooleanField(default=False)
int1_name = models.CharField(max_length=60, blank=True, null=True)
int1_default = models.IntegerField(blank=True, null=True)
float1_name = models.CharField(max_length=60, blank=True, null=True)
float1_default = models.FloatField(blank=True, null=True)
date1_name = models.CharField(max_length=60, blank=True, null=True)
class ProductData(models.Model):
created = models.DateTimeField(default=datetime.now)
created_by = models.ForeignKey(User)
item = models.ManyToManyField(Item)
bool1_val = models.BooleanField(default=False)
int1_val = models.IntegerField(blank=True, null=True)
float1_val = models.FloatField(blank=True, null=True)
date1_val = models.DateField(blank=True, null=True)
class Item(models.Model):
product = models.ForeignKey(Product)
business = models.ForeignKey(Business)
And I have put in the following data into the database:
# pseudo code
Product(1,'Toothpaste','TP','Is the toothpaste white?',1,,,'Weight',1,)
Product(1,'Milk','MLK',,,,,'Litres',2,'Best Before')
I want to be able to build a form for ProductData based on the variables defined in Product (create-a-form-based-on-the-values-from-another-table). I want something like this:
class ProductDataForm(ModelForm):
def __init__(self,p,*args,**kwargs):
super(ProductDataForm, self).__init__(*args, **kwargs)
# if the values isn't set in the product
if p.bool1_name is None:
# hide the input
self.fields['bool1_val'].widget = forms.CharField(required=False)
else:
# else make sure the name the field name is the product name
self.fields['bool1_val'].widget = forms.BooleanField(label=p.bool1_name)
...
But I'm having a problem passing an instance of Product to ProductDataForm. Others have said I could use a BaseModelFormSet but literature on this is sketchy and I'm not to sure how to apply it.
EDIT
If I create an array of all the fields I DON'T want to show in ProductDataForm's init how can I pass these to the Meta class's exclude. Like so:
class ProductDataForm(ModelForm):
def __init__(self,p,*args,**kwargs):
super(ProductDataForm, self).__init__(*args, **kwargs)
tempExclude = []
if not p.bool1_name:
tempExclude.append('bool1_val')
else:
self.fields['bool1_val'].label = p.bool1_name
self.Meta.exclude = tempExclude
class Meta:
model = ProductData
exclude = []
EDIT
I'm now trying to store the fields I want to exclude in the setting.py file like so:
# settings.py
SUBITEM_EXCLUDE_FIELDS = ['dave']
# views.py
def new_product_data_view(request,product='0'):
try:
i_fpKEY = int(product)
except ValueError:
raise Http404()
if not i_fpKEY:
t_fp = Product.objects.filter(active=1).order_by('id')[0]
else:
t_fp = Product.objects.get(id=i_fpKEY)
FieldsToExcludeFromProductDataForm(t_fp)
print "views.py > PRODUCTDATA_EXCLUDE_FIELDS = "+str(PRODUCTDATA_EXCLUDE_FIELDS)
siForm = ProductDataForm(t_fp, request.POST, auto_id='si_%s')
return render_to_response(...)
# middleware.py
def FieldsToExcludeFromProductDataForm(tempFP):
excludedFields = ['created','created_by','item']
if not tempFP.bool1_name:
excludedFields.append('bool1_val')
if not tempFP.int1_name:
excludedFields.append('int1_val')
...
for val in excludedFields:
PRODUCTDATA_EXCLUDE_FIELDS.append(val)
print "middleware.py > PRODUCTDATA_EXCLUDE_FIELDS = "+str(PRODUCTDATA_EXCLUDE_FIELDS)
# forms.py
class ProductDataForm(ModelForm):
# Only renames the fields based on whether the product has a name
# for the field. The exclusion list is made in middleware
def __init__(self,fp,*args,**kwargs):
super(ProductDataForm, self).__init__(*args, **kwargs)
if fp.bool1_name:
self.fields['bool1_val'].label = fp.bool1_name
if fp.int1_name:
self.fields['int1_val'].label = fp.int1_name
class Meta:
def __init__(self,*args,**kwargs):
super(Meta, self).__init__(*args, **kwargs)
print 'Meta > __init__ > PRODUCTDATA_EXCLUDE_FIELDS = '+str(PRODUCTDATA_EXCLUDE_FIELDS)
model = ProductData
print 'Meta > PRODUCTDATA_EXCLUDE_FIELDS = '+str(PRODUCTDATA_EXCLUDE_FIELDS)
#exclude = PRODUCTDATA_EXCLUDE_FIELDS
But terminal shows that the Meta class gets processed very early on and therefore can't get the newly amended PRODUCTDATA_EXCLUDE_FIELDS :
Meta > PRODUCTDATA_EXCLUDE_FIELDS = ['dave']
[11/Jul/2011 15:51:31] "GET /page/profile/1/ HTTP/1.1" 200 11410
middleware.py > PRODUCTDATA_EXCLUDE_FIELDS = ['dave', 'created', 'created_by', 'item', 'bool1_val', 'int1_val']
views.py > PRODUCTDATA_EXCLUDE_FIELDS = ['dave', 'created', 'created_by', 'item', 'bool1_val', 'int1_val']
[11/Jul/2011 15:51:32] "GET /item/new/ HTTP/1.1" 200 5028
[11/Jul/2011 15:51:32] "GET /client/1/ HTTP/1.1" 200 5445
[11/Jul/2011 15:51:32] "GET /client/view/1/ HTTP/1.1" 200 3082
Why bother with exclude, and metaclasses - just delete fields you want to exclude:
class ProductDataForm(ModelForm):
__init__():
...
for field_name in PRODUCTDATA_EXCLUDE_FIELDS:
del self.fields[field_name]
...
Maybe it's not quite right, but it's simple, and it works.
Your idea was spot on but then you tried to implement it in a very roundabout way.
Python lets you create classes dynamically at runtime using the three argument form of the type() function.
The common way to make use of this is with a factory function and Django provides just that in the form of django.forms.models.modelform_factory.
This function isn't documented but that seem to be in progress. It's from the same family of functions as modelformset_factory and inlineformset_factory which are documented so I'd say it's safe to use.
The signature is
modelform_factory(model, form=ModelForm, fields=None, exclude=None, formfield_callback=None)
model, exclude and fields are equivalent to what you would normally declare in the form's inner Meta class (and that's how it's implemented).
Now, to use this in your example you'll want to change your approach a bit. First import the factory function.
from django.forms.models import modelformset_factory
Then use your view function or class to:
Get the product instance.
Generate a list of excluded fields based on the product instance (I'll call this excludedFields).
Create a form class:
formClass = modelform_factory(ProductData, excludes=excludedFields)
Initialise your form as usual, but using formClass instead of a predefined form (ProductDataForm)
form = formClass() or form = formClass(request.POST) etc...