Django - passing extra arguments into default callable function - django

Original Title: cannot get import to work properly
I am trying to generate a function which will create a random alphanumeric number and make it as default value of a model field in django.
So for a single model I did like this:
# utils.py
def generate_random_unique_code_for_model():
from .models import mymodel
while 1:
code = random_code() #my custom function to generate random alphanumeric
try:
mymodel.objects.get(myfield=code)
except mymodel.DoesNotExist:
return code
#models.py
class mymodel(models.Model):
#other fields
myfield = models.CharField(default=generate_random_unique_code_for_model)
This code works fine, but now I have to provide similar function for another model, so to follow the DRY principle I am trying to make the model , fieldnames dynamic. So basically I am trying to accomplish from some_app.models import some_model inside my generate_random_unique_code_for_model function .
def get_model(location, model_name):
try:
module = __import__('.'.join(location), globals(), locals(), [model_name], -1)
model_instance = getattr(module, model_name)
except:
raise ImportError(_('Could not import %(model_name)s from %(location)s') % {'model_name': model_name,
'location': '.'.join(location)})
return model_instance
def generate_random_unique_code_for_model(location, model_name, field_name):
model_object = get_model(location, model_name)
kwargs = {field_name: ''}
while 1:
code = random_code()
kwargs[field_name] = code
try:
model_object.objects.get(**kwargs)
except model_object.DoesNotExist:
return code
#models.py
class mymodel_name(models.Model):
#other fields
myfield_name = models.CharField(default=generate_random_unique_code_for_model(['myapp_name', 'mymodel_name'], 'myfield_name'))
While debuggin, when I do dir(module) while debugging I don't see mymodel_name in the list. Any workarounds please?

The problem was default takes a callable function so whenever an instance of model is instantiated the default function is called. But since I called the function in the second case whenever the server is started and models are loaded it was trying to load model before the model class was created. So the problem comes down to pass a callable function with parameters to default which is not possible as of now. So what I did is this:
def make_random():
return generate_random_unique_code_for_model(['myapp_name', 'mymodel_name'], 'myfield_name')
class mymodel_name(models.Model):
#other fields
myfield_name = models.CharField(default=make_random)

Related

How to call a function with context in django CB list view?

This is my view:
class viewbloglistview(LoginRequiredMixin,ListView):
model = Blog
paginate_by = 6
def get_template_names(self):
if True:
return ['blog/view_blogs.html']
else:
return ['blog/blog_list.html']
def get_queryset(self):
return Blog.objects.all().order_by('-blog_views')[:20]
def get_context_data(self, **kwargs):
context = super(viewbloglistview, self).get_context_data(**kwargs)
context['categories_list'] = categories.objects.all()
return context
This is my function in models.py file:
def categories_count(self):
categories_count = categories.objects.annotate(blog_count=Count('blogs')).values_list('Title','blog_count')
return categories_count
I want call the function in my views with a context name to render the activity in my template..
Can anyone please help me out to solve this problem??
Thank you
This is a python problem, your question is unclear but based on what you said:
Case the function in in your model.py alone:
from . import model.py
// code
categories_count()
Case the function is a method in a class as it is shown on you code with the self parameter in it:
from . import model.py
// code
classname.categories_count()
Assuming that you have named your class as 'categories' (which should have been named as Category in the first place),
categories_count should have been in a manager as you are querying in a class level. Say you don't want a manager and want to keep the code inside the model, then you can use it as a class method.
#classmethod
def categories_count(cls):
return cls.objects.annotate(blog_count=Count('blogs')).values_list('Title','blog_count')
and in the views use it as
categories.categories_count()
Just remember that the regular methods with the 'self' argument like the one you have, should only be used when you are dealing with a single instance, not when you are accessing the class itself.

Django MultiValueField

I'm struggling with some Django, where I want to make a custom MultiValueField combined with MultiWidget. I've read misc. tutorials, but it seem I'm missing something - most of them were quite old, which I suspect could be the reason.
I'm using Django 1.10.
Goal: Make a custom field that provides three dropdowns for a form. So far, no requirements to the contents of the dropdowns - first I just want to see them in my form :-)
I have a fields.py file, containing:
from django import forms
from widgets import MyCustomWidget
class MyCustomField(forms.MultiValueField):
widget = MyCustomWidget
def __init__(self, *args, **kwargs):
fields = (
forms.CharField(max_length=31),
forms.CharField(max_length=31),
forms.CharField(max_length=31),
)
super(MyCustomField, self).__init__(fields, *args, **kwargs)
def compress(self, data_list):
return "-".join(data_list)
And then there's a widgets.py, containing:
import re
from django import forms
class MyCustomWidget(forms.MultiWidget):
def __init__(self, attrs=None):
widgets = (
forms.widgets.Select(attrs=attrs, choices=[("1", "1")]),
forms.widgets.Select(attrs=attrs, choices=[("2", "2")]),
forms.widgets.Select(attrs=attrs, choices=[("3", "3")]),
)
super(MyCustomWidget, self).__init__(widgets, attrs)
def decompress(self, value):
if value:
return re.split(r"\-", value)
return [None, None, None]
forms.py:
from django.forms import ModelForm
from django import forms
class MyCustomForm(forms.ModelForm):
class Meta:
model = MyCustomModel
fields = ("name")
name = forms.CharField(widget=MyCustomField)
This works fine when migrating, but I try to view the form, I get this error:
'MyCustomField' object has no attribute 'is_hidden'
I have tried to implement this attribute in MyCustomField, but then I get another error:
'MyCustomField' object has no attribute 'attrs'
These attributes should be provided by forms.MultiValueField, as far as I understand - thus I shouldn't need to write them myself.
In the template, I'm just using "{{ form }}" as I wan't to use Django's default layout.
I'm going nuts here and hope someone is able to help to the right path :-)
Kind regards,
Kasper
I believe your mistake is on the line where you set the name
name = forms.CharField(widget=MyCustomField). I haven't tested the codes thou.
from django.forms import ModelForm
from django import forms
class MyCustomForm(forms.ModelForm):
class Meta:
model = MyCustomModel
fields = ("name")
name = MyCustomField # This I believe is will fix your error

Django custom field widget and widget behaviour for a custom "ListField"

I'm creating the following custom field based off How to create list field in django
import re
from django.db import models
from django.forms.widgets import TextInput
class ListField(models.TextField):
__metaclass__ = models.SubfieldBase
description = "Stores a python list"
widget = TextInput
def __init__(self, *args, **kwargs):
super(ListField, self).__init__(*args, **kwargs)
def to_python(self, value):
if not value:
return []
return filter(None, re.split(r'\,|\s*', value))
def get_prep_value(self, value):
if value is None:
return value
return ', '.join(value)
def value_to_string(self, obj):
value = self._get_val_from_obj(obj)
return self.get_db_prep_value(value)
from south.modelsinspector import add_introspection_rules
add_introspection_rules([], ["^cflowportal\.utils\.modelutils\.ListField"])
Basically, what I want to achieve is a field where you write something like "1, asd, asdf fdgd", it stores it as such in the database but when retrieved it should return that string as an array and when given an array it should convert it back to a comma-seperated string.
I'm still not sure if what I've written so far works, but I'm having trouble displaying it as an input field and not a textarea even if I've set widget=TextInput.
So, how do I show it in the admin with the same input used by the standard CharField?
How can I customize it so that it displays a comma-separated string when showed on such input, but is given back as a Python List when accessed elsewhere?
Thanks
The following is a method to realize what you want
from django.db import models
class Blog(models.Model):
title = models.CharField(max_length=256)
labels = models.TextField()
def get_labels(self):
return self.content.split('\n')
def set_labels(self,value):
if isinstance(value,list) or isinstance(value,tuple) or isinstance(value,set):
content = '\n'.join(value)
else:
content = value
self.content = content
You can regard labels as a ListField, set value use obj.set_labels(list) function, and get value use obj.get_labels()
It act as a List Field, and admin site will run as a normal TextField.
This is what I did, but a better solution is excepted.
and a better way to do this is using save_model in admin.py:
class BlogAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
# extra data handling, prevent data convert
obj.save()

How to map model fields with form field in Django

We have one application containing models.py which contains n no. of classes that inherits base class.We want to create form which dynamically takes value from user n saves in db but problem is that we want to use django form fields instead of django model forms.
As we know there are some fields missing in django forms such as PositiveIntegerField, CommaSeparetedIntegerFields etc. How can we achieve this using django form fields?
If we write follwing code in shell.
from djnago.db import models
mvar = models.PositiveIntegerFields()
from django import forms
fvar = forms.PositiveIntegerFields()
AttributeError: 'module' object has no attribute 'PositiveIntegerField'
forms.py
from django import forms
class ContextForm(forms.Form):
def __init__(self, rdict, *args, **kwargs):
super(ContextForm, self).__init__(*args, **kwargs)
for key in rdict.keys():
self.fields['%s' % str(key)] = getattr(forms,rdict.get(key))()
rdict = {'address': 'CharField','phone': 'CharField', 'Salary': 'PositiveIntegerField','first name': 'CharField','last name':'CharField'}
Looking at the source, all the field does is call the default form field with a keyword argument: min_value.
class PositiveIntegerField(IntegerField):
description = _("Positive integer")
def get_internal_type(self):
return "PositiveIntegerField"
def formfield(self, **kwargs):
defaults = {'min_value': 0}
defaults.update(kwargs)
return super(PositiveIntegerField, self).formfield(**defaults)
Therefore what you are looking for is merely
from django import forms
fvar = forms.IntegerField(min_value=0)
fvar.clean(-1)
# ValidationError: [u'Ensure this value is greater than or equal to 0.']
As for CommaSeparatedIntegerField, it looks like a CharField with some django.core.validators.validate_comma_separated_integer_list passed in.
f = forms.CharField(validators=[django.core.validators.validate_comma_separated_integer_list])
f.clean('1,2,3')
All this does is make sure the passed in string is '^[\d,]+$'. The field doesn't even do any python conversions... it doesn't really seem to save much time if just validates form input. Indeed, there's a comment that says "maybe move to contrib". Agreed..
Decided to look into this for fun. Here's a ModelForm generator that overrides model fields with new fields... It doesn't yet handle kwargs. It was just the first method I could think of to do this.. without looking into modelform generation itself. It constructs a regular ModelForm that modifies the form /after/ initialization.
MODEL_FIELD_MAP = {
models.IntegerField: forms.CharField,
# change all IntegerField to forms.CharField
}
def modelform_generator(mymodel):
class MyModelForm(forms.ModelForm):
class Meta:
model = mymodel
def __init__(self, *args, **kwargs):
super(MyModelForm, self).__init__(*args, **kwargs)
for name, form_field in self.fields.items():
try:
model_field = self._meta.model._meta.get_field_by_name(name)[0]
# is this a model field?
field_override = MODEL_FIELD_MAP.get(model_field.__class__)
# do we have this model field mapped to a form field?
if field_override:
self.fields[name] = field_override()
# set the form field to the target field class
except models.FieldDoesNotExist:
pass
return MyModelForm

Django: how to use custom manager in get_previous_by_FOO()?

I have a simple model MyModel with a date field named publication_date. I also have a custom manager that filters my model based on this date field.
This custom manager is accessible by .published and the default one by .objects.
from datetime import date, datetime
from django.db import models
class MyModelManager(models.Manager):
def get_query_set(self):
q = super(MyModelManager, self).get_query_set()
return q.filter(publication_date__lte=datetime.now())
class MyModel(models.Model):
...
publication_date = models.DateField(default=date.today())
objects = models.Manager()
published = MyModelManager()
This way, I got access to all objects in the admin but only to published ones in my views (using MyModel.published.all() queryset).
I also have
def get_previous(self):
return self.get_previous_by_publication_date()
def get_next(self):
return self.get_next_by_publication_date()
which I use in my templates: when viewing an object I can link to the previous and next object using
{{ object.get_previous }}
The problem is: this returns the previous object in the default queryset (objects) and not in my custom one (published).
I wonder how I can do to tell to this basic model functions (get_previous_by_FOO) to use my custom manager.
Or, if it's not possible, how to do the same thing with another solution.
Thanks in advance for any advice.
Edit
The view is called this way in my urlconf, using object_detail from the generic views.
(r'^(?P<slug>[\w-]+)$', object_detail,
{
'queryset': MyModel.published.all(),
'slug_field': 'slug',
},
'mymodel-detail'
),
I'm using Django 1.2.
In fact, get_next_or_previous_by_FIELD() Django function (which is used by get_previous_by_publication_date...) uses the default_manager.
So I have adapted it to reimplement my own utility function
def _own_get_next_or_previous_by_FIELD(self, field, is_next):
if not self.pk:
raise ValueError("get_next/get_previous cannot be used on unsaved objects.")
op = is_next and 'gt' or 'lt'
order = not is_next and '-' or ''
param = smart_str(getattr(self, field.attname))
q = Q(**{'%s__%s' % (field.name, op): param})
q = q|Q(**{field.name: param, 'pk__%s' % op: self.pk})
qs = MyModel.published.filter(q).order_by('%s%s' % (order, field.name), '%spk' % order)
try:
return qs[0]
except IndexError:
def get_previous(self):
return self._own_get_next_or_previous_by_FIELD(MyModel._meta.fields[4], False)
def get_next(self):
return self._own_get_next_or_previous_by_FIELD(MyModel._meta.fields[4], True)
This is not a very clean solution, as I need to hardcode the queryset and the field used, but at least it works.