I have a model with a ImageField:
class Usr( User ):
idusuario = models.AutoField( primary_key = True )
usuario = models.CharField( blank = False, unique = True, max_length = 50 )
contraseña = models.CharField( blank = False, max_length = 250 )
telefono = models.CharField( max_length = 15, blank = True, null = True )
celular = models.CharField( max_length = 15, blank = True, null = True )
fotografia = models.ImageField( blank = True, null = True, upload_to = 'usuarios' )
depende_de = models.ForeignKey( 'self', on_delete = models.CASCADE, related_name = '+', blank = True, null = True )
def __unicode__( self ):
return self.get_full_name()
def __str__( self ):
return self.get_full_name()
And its form (a ModelForm ):
class RegUsuario( forms.ModelForm ):
class Meta:
model = Usr
fields = [
'usuario',
'contraseña',
'is_active',
'is_superuser',
'first_name',
'last_name',
'email',
'telefono',
'celular',
'fotografia',
'groups',
'depende_de'
]
In views (vw_usuario.py) I have:
#valida_acceso( [ 'usr.agregar_usuarios_usuario' ] )
def new( request ):
if 'POST' == request.method:
frm = RegUsuario( request.POST, files = request.FILES )
if frm.is_valid():
obj = frm.save( commit = False )
obj.username = obj.usuario
obj.set_password( obj.contraseña )
obj.save()
for g in request.POST.getlist( 'groups' ):
obj.groups.add( g )
obj.save()
return HttpResponseRedirect( reverse( 'usuario_ver', kwargs = { 'pk' : obj.pk } ) )
frm = RegUsuario( request.POST or None )
return render( request, 'global/form.html', {
'menu_main' : Usr.objects.filter( id = request.user.pk )[ 0 ].main_menu_struct(),
'footer' : True,
'titulo' : 'Usuarios',
'titulo_descripcion' : 'Nuevo',
'frm' : frm
} )
And in urls.py:
urlpatterns = [
...
path( 'usuarios/nuevo/', vw_usuario.new, name = "usuario_nuevo" ),
...
]
If I go to http://example.com/usuarios/nuevo/ in the browser I can the form displayed as well, then I fill the fields and submit the form, and I got the exception:
Page not found (404)
Request Method: POST
Request URL: http://example.com/usuarios/nuevo/
Raised by: seguridad.mkitsafe.validacion
Using the URLconf defined in example.urls, Django tried these URL patterns, in this order:
...
usuarios/eliminar/<pk>/ [name= usuario_eliminar ]
usuarios/nuevo/ [name= usuario_nuevo ]
usuarios/<pk>/ [name= usuario_ver ]
...
The current path, nuevo/, didn t match any of these.
I don't understand why the url is taken as nuevo/ instead of usuarios/nuevo/
In the form template I'm setting method = "post", enctype="multipart/form-data" and action="" (I've also tried action="usuarios/nuevo" and action="http://example.com/usuarios/nuevo/")
The path for MEDIA_ROOT exists and has enough perms.
The issue happens if I fill in the form the ImageField field or not.
I noticed that if I remove the field in the modelform, everything works as well.
The source code for seguridad.mkitsafe.validacion is:
def valida_acceso( permisos = None ):
url_error = 'seguridad_inicio'
url_autenticacion = 'seguridad_login'
def _valida_acceso( vista ):
def validacion( *args, **kwargs ):
usuario = args[ 0 ].user
if not usuario.is_authenticated:
print_error( "Vista {} negada por autenticación".format( vista.__name__ ), "Exec Info" )
return HttpResponseRedirect( reverse( url_autenticacion ) )
if permisos is None:
return vista( *args, **kwargs )
perms = []
for perm in permisos:
permiso = Permiso.get_from_package_codename( perm )
if( permiso is None ):
print_error( "No se ha encontrado el permiso: " + perm )
else:
perms.append( permiso.perm() )
desc = permiso.descendencia()
for p in desc:
perms.append( p.perm() )
for perm in perms:
p = "{}.{}".format( perm.content_type.app_label, perm.codename )
if usuario.has_perm( p ):
return vista( *args, **kwargs )
print_error( "Vista {} negada por permisos {}".format( vista.__name__, permisos ), "Exec Info" )
return HttpResponseRedirect( reverse( url_error ) )
return validacion
return _valida_acceso
And it is a decorator which verify perms and authentication.
If I remove this decorator the excepion changes in the Raised by to seguridad.vw_usuario.new
Page not found (404)
Request Method: POST
Request URL: http://example.com/usuarios/nuevo/
Raised by: seguridad.vw_usuario.new
Using the URLconf defined in example.urls, Django tried these URL patterns, in this order:
admin/
...
usuarios/actualizar/<pk>/ [name='usuario_actualizar']
usuarios/eliminar/<pk>/ [name='usuario_eliminar']
usuarios/nuevo/ [name='usuario_nuevo']
usuarios/<pk>/ [name='usuario_ver']
usuarios/ [name='usuario_inicio']
...
The current path, nuevo/, didn't match any of these.
I also noticed that when the error is raised and the decorator is active the decorator or the view function is not called.
I also tried to copy a file in MEDIA_ROOT path with an script and it worked as well, so the script has right access to this path.
Any comment about the issue?
My template.html:
{% for x in post.tags.all %}
<a href="{% url 'blog:post_list_by_tag' x.slug %}">
{{ x.name }}
</a>
{% if not forloop.last %}, {% endif %}
{% endfor %}
I haven't space at the end of the lines and I haven't space in the name (in database) but the output is:
tag1 , tag2 , tag3
with a space between name and comma and a space at the end. Even with one tag there's a space at the end. I use taggit, maybe the problem is there.
Also the links underline even the white spaces when after there's the comma (so not at the end). If I write {{ x.name }}</a> the spaces are there but the links underline only the tags, not the spaces.
In myview
print(post.tags) => AttributeError: '_TaggableManager' object has no attribute 'name'
print(post.tags.all) =>
<bound method BaseManager.all of <taggit.managers._TaggableManager object at 0x03EFEA70>>
Mymodel.py:
class Post(models.Model):
author = models.ForeignKey('auth.User', blank=True,
on_delete=models.PROTECT, verbose_name=_('autore'))
title = models.CharField(_('titolo'), max_length=32)
text = models.TextField(_('testo'))
created_date = models.DateTimeField(_('creato il'),
default=timezone.now)
published_date = models.DateTimeField(_('pubblicato il'),
blank=True, null=True)
likes = models.ManyToManyField(settings.AUTH_USER_MODEL,
related_name='posts_liked', blank=True, verbose_name=_('piace a'))
dislikes = models.ManyToManyField(settings.AUTH_USER_MODEL,
related_name='posts_disliked', blank=True, verbose_name=_('non piace a'))
tags = TaggableManager()
views = models.IntegerField(_('visite'), default=0)
block_comment = models.BooleanField(default=False)
def publish(self):
self.published_date = timezone.now()
self.save()
def __str__(self):
return self.title
class Meta:
verbose_name = pgettext('singolare', 'post')
verbose_name_plural = pgettext('plurale', 'post')
...
taggit.managers.py:
...
class _TaggableManager(models.Manager):
def __init__(self, through, model, instance, prefetch_cache_name):
self.through = through
self.model = model
self.instance = instance
self.prefetch_cache_name = prefetch_cache_name
self._db = None
def is_cached(self, instance):
return self.prefetch_cache_name in instance._prefetched_objects_cache
def get_queryset(self, extra_filters=None):
try:
return self.instance._prefetched_objects_cache[self.prefetch_cache_name]
except (AttributeError, KeyError):
kwargs = extra_filters if extra_filters else {}
return self.through.tags_for(self.model, self.instance, **kwargs)
def get_prefetch_queryset(self, instances, queryset=None):
if queryset is not None:
raise ValueError("Custom queryset can't be used for this lookup.")
instance = instances[0]
from django.db import connections
db = self._db or router.db_for_read(instance.__class__, instance=instance)
fieldname = ('object_id' if issubclass(self.through, CommonGenericTaggedItemBase)
else 'content_object')
fk = self.through._meta.get_field(fieldname)
query = {
'%s__%s__in' % (self.through.tag_relname(), fk.name):
set(obj._get_pk_val() for obj in instances)
}
join_table = self.through._meta.db_table
source_col = fk.column
connection = connections[db]
qn = connection.ops.quote_name
qs = self.get_queryset(query).using(db).extra(
select={
'_prefetch_related_val': '%s.%s' % (qn(join_table), qn(source_col))
}
)
return (qs,
attrgetter('_prefetch_related_val'),
lambda obj: obj._get_pk_val(),
False,
self.prefetch_cache_name)
# Django < 1.6 uses the previous name of query_set
get_query_set = get_queryset
get_prefetch_query_set = get_prefetch_queryset
def _lookup_kwargs(self):
return self.through.lookup_kwargs(self.instance)
#require_instance_manager
def add(self, *tags):
db = router.db_for_write(self.through, instance=self.instance)
tag_objs = self._to_tag_model_instances(tags)
new_ids = set(t.pk for t in tag_objs)
# NOTE: can we hardcode 'tag_id' here or should the column name be got
# dynamically from somewhere?
vals = (self.through._default_manager.using(db)
.values_list('tag_id', flat=True)
.filter(**self._lookup_kwargs()))
new_ids = new_ids - set(vals)
signals.m2m_changed.send(
sender=self.through, action="pre_add",
instance=self.instance, reverse=False,
model=self.through.tag_model(), pk_set=new_ids, using=db,
)
for tag in tag_objs:
self.through._default_manager.using(db).get_or_create(
tag=tag, **self._lookup_kwargs())
signals.m2m_changed.send(
sender=self.through, action="post_add",
instance=self.instance, reverse=False,
model=self.through.tag_model(), pk_set=new_ids, using=db,
)
def _to_tag_model_instances(self, tags):
"""
Takes an iterable containing either strings, tag objects, or a mixture
of both and returns set of tag objects.
"""
db = router.db_for_write(self.through, instance=self.instance)
str_tags = set()
tag_objs = set()
for t in tags:
if isinstance(t, self.through.tag_model()):
tag_objs.add(t)
elif isinstance(t, six.string_types):
str_tags.add(t)
else:
raise ValueError(
"Cannot add {0} ({1}). Expected {2} or str.".format(
t, type(t), type(self.through.tag_model())))
if getattr(settings, 'TAGGIT_CASE_INSENSITIVE', False):
# Some databases can do case-insensitive comparison with IN, which
# would be faster, but we can't rely on it or easily detect it.
existing = []
tags_to_create = []
for name in str_tags:
try:
tag = (self.through.tag_model()._default_manager
.using(db)
.get(name__iexact=name))
existing.append(tag)
except self.through.tag_model().DoesNotExist:
tags_to_create.append(name)
else:
# If str_tags has 0 elements Django actually optimizes that to not
# do a query. Malcolm is very smart.
existing = (self.through.tag_model()._default_manager
.using(db)
.filter(name__in=str_tags))
tags_to_create = str_tags - set(t.name for t in existing)
tag_objs.update(existing)
for new_tag in tags_to_create:
tag_objs.add(
self.through.tag_model()._default_manager
.using(db)
.create(name=new_tag))
return tag_objs
#require_instance_manager
def names(self):
return self.get_queryset().values_list('name', flat=True)
#require_instance_manager
def slugs(self):
return self.get_queryset().values_list('slug', flat=True)
#require_instance_manager
def set(self, *tags, **kwargs):
"""
Set the object's tags to the given n tags. If the clear kwarg is True
then all existing tags are removed (using `.clear()`) and the new tags
added. Otherwise, only those tags that are not present in the args are
removed and any new tags added.
"""
db = router.db_for_write(self.through, instance=self.instance)
clear = kwargs.pop('clear', False)
if clear:
self.clear()
self.add(*tags)
else:
# make sure we're working with a collection of a uniform type
objs = self._to_tag_model_instances(tags)
# get the existing tag strings
old_tag_strs = set(self.through._default_manager
.using(db)
.filter(**self._lookup_kwargs())
.values_list('tag__name', flat=True))
new_objs = []
for obj in objs:
if obj.name in old_tag_strs:
old_tag_strs.remove(obj.name)
else:
new_objs.append(obj)
self.remove(*old_tag_strs)
self.add(*new_objs)
#require_instance_manager
def remove(self, *tags):
if not tags:
return
db = router.db_for_write(self.through, instance=self.instance)
qs = (self.through._default_manager.using(db)
.filter(**self._lookup_kwargs())
.filter(tag__name__in=tags))
old_ids = set(qs.values_list('tag_id', flat=True))
signals.m2m_changed.send(
sender=self.through, action="pre_remove",
instance=self.instance, reverse=False,
model=self.through.tag_model(), pk_set=old_ids, using=db,
)
qs.delete()
signals.m2m_changed.send(
sender=self.through, action="post_remove",
instance=self.instance, reverse=False,
model=self.through.tag_model(), pk_set=old_ids, using=db,
)
#require_instance_manager
def clear(self):
db = router.db_for_write(self.through, instance=self.instance)
signals.m2m_changed.send(
sender=self.through, action="pre_clear",
instance=self.instance, reverse=False,
model=self.through.tag_model(), pk_set=None, using=db,
)
self.through._default_manager.using(db).filter(
**self._lookup_kwargs()).delete()
signals.m2m_changed.send(
sender=self.through, action="post_clear",
instance=self.instance, reverse=False,
model=self.through.tag_model(), pk_set=None, using=db,
)
def most_common(self, min_count=None, extra_filters=None):
queryset = self.get_queryset(extra_filters).annotate(
num_times=models.Count(self.through.tag_relname())
).order_by('-num_times')
if min_count:
queryset = queryset.filter(num_times__gte=min_count)
return queryset
#require_instance_manager
def similar_objects(self):
lookup_kwargs = self._lookup_kwargs()
lookup_keys = sorted(lookup_kwargs)
qs = self.through.objects.values(*six.iterkeys(lookup_kwargs))
qs = qs.annotate(n=models.Count('pk'))
qs = qs.exclude(**lookup_kwargs)
qs = qs.filter(tag__in=self.all())
qs = qs.order_by('-n')
# TODO: This all feels like a bit of a hack.
items = {}
if len(lookup_keys) == 1:
# Can we do this without a second query by using a select_related()
# somehow?
f = _get_field(self.through, lookup_keys[0])
remote_field = _remote_field(f)
rel_model = _related_model(_remote_field(f))
objs = rel_model._default_manager.filter(**{
"%s__in" % remote_field.field_name: [r["content_object"] for r in qs]
})
for obj in objs:
items[(getattr(obj, remote_field.field_name),)] = obj
else:
preload = {}
for result in qs:
preload.setdefault(result['content_type'], set())
preload[result["content_type"]].add(result["object_id"])
for ct, obj_ids in preload.items():
ct = ContentType.objects.get_for_id(ct)
for obj in ct.model_class()._default_manager.filter(pk__in=obj_ids):
items[(ct.pk, obj.pk)] = obj
results = []
for result in qs:
obj = items[
tuple(result[k] for k in lookup_keys)
]
obj.similar_tags = result["n"]
results.append(obj)
return results
# _TaggableManager needs to be hashable but BaseManagers in Django 1.8+ overrides
# the __eq__ method which makes the default __hash__ method disappear.
# This checks if the __hash__ attribute is None, and if so, it reinstates the original method.
if models.Manager.__hash__ is None:
__hash__ = object.__hash__
class TaggableManager(RelatedField, Field):
# Field flags
many_to_many = True
many_to_one = False
one_to_many = False
one_to_one = False
_related_name_counter = 0
def __init__(self, verbose_name=_("Tags"),
help_text=_("A comma-separated list of tags."),
through=None, blank=False, related_name=None, to=None,
manager=_TaggableManager):
self.through = through or TaggedItem
self.swappable = False
self.manager = manager
rel = TaggableRel(self, related_name, self.through, to=to)
Field.__init__(
self,
verbose_name=verbose_name,
help_text=help_text,
blank=blank,
null=True,
serialize=False,
rel=rel,
)
# NOTE: `to` is ignored, only used via `deconstruct`.
def __get__(self, instance, model):
if instance is not None and instance.pk is None:
raise ValueError("%s objects need to have a primary key value "
"before you can access their tags." % model.__name__)
manager = self.manager(
through=self.through,
model=model,
instance=instance,
prefetch_cache_name=self.name
)
return manager
def deconstruct(self):
"""
Deconstruct the object, used with migrations.
"""
name, path, args, kwargs = super(TaggableManager, self).deconstruct()
# Remove forced kwargs.
for kwarg in ('serialize', 'null'):
del kwargs[kwarg]
# Add arguments related to relations.
# Ref: https://github.com/alex/django-taggit/issues/206#issuecomment-37578676
rel = _remote_field(self)
if isinstance(rel.through, six.string_types):
kwargs['through'] = rel.through
elif not rel.through._meta.auto_created:
kwargs['through'] = "%s.%s" % (rel.through._meta.app_label, rel.through._meta.object_name)
related_model = _related_model(rel)
if isinstance(related_model, six.string_types):
kwargs['to'] = related_model
else:
kwargs['to'] = '%s.%s' % (related_model._meta.app_label, related_model._meta.object_name)
return name, path, args, kwargs
def contribute_to_class(self, cls, name):
if VERSION < (1, 7):
self.name = self.column = self.attname = name
else:
self.set_attributes_from_name(name)
self.model = cls
self.opts = cls._meta
cls._meta.add_field(self)
setattr(cls, name, self)
if not cls._meta.abstract:
# rel.to renamed to remote_field.model in Django 1.9
if VERSION >= (1, 9):
if isinstance(self.remote_field.model, six.string_types):
def resolve_related_class(cls, model, field):
field.remote_field.model = model
lazy_related_operation(
resolve_related_class, cls, self.remote_field.model, field=self
)
else:
if isinstance(self.rel.to, six.string_types):
def resolve_related_class(field, model, cls):
field.rel.to = model
add_lazy_relation(cls, self, self.rel.to, resolve_related_class)
if isinstance(self.through, six.string_types):
if VERSION >= (1, 9):
def resolve_related_class(cls, model, field):
self.through = model
self.remote_field.through = model
self.post_through_setup(cls)
lazy_related_operation(
resolve_related_class, cls, self.through, field=self
)
else:
def resolve_related_class(field, model, cls):
self.through = model
_remote_field(self).through = model
self.post_through_setup(cls)
add_lazy_relation(
cls, self, self.through, resolve_related_class
)
else:
self.post_through_setup(cls)
def get_internal_type(self):
return 'ManyToManyField'
def __lt__(self, other):
"""
Required contribute_to_class as Django uses bisect
for ordered class contribution and bisect requires
a orderable type in py3.
"""
return False
def post_through_setup(self, cls):
if RelatedObject is not None: # Django < 1.8
self.related = RelatedObject(cls, self.model, self)
self.use_gfk = (
self.through is None or issubclass(self.through, CommonGenericTaggedItemBase)
)
# rel.to renamed to remote_field.model in Django 1.9
if VERSION >= (1, 9):
if not self.remote_field.model:
self.remote_field.model = self.through._meta.get_field("tag").remote_field.model
else:
if not self.rel.to:
self.rel.to = self.through._meta.get_field("tag").rel.to
if RelatedObject is not None: # Django < 1.8
self.related = RelatedObject(self.through, cls, self)
if self.use_gfk:
tagged_items = GenericRelation(self.through)
tagged_items.contribute_to_class(cls, 'tagged_items')
for rel in cls._meta.local_many_to_many:
if rel == self or not isinstance(rel, TaggableManager):
continue
if rel.through == self.through:
raise ValueError('You can\'t have two TaggableManagers with the'
' same through model.')
def save_form_data(self, instance, value):
getattr(instance, self.name).set(*value)
def formfield(self, form_class=TagField, **kwargs):
defaults = {
"label": capfirst(self.verbose_name),
"help_text": self.help_text,
"required": not self.blank
}
defaults.update(kwargs)
return form_class(**defaults)
def value_from_object(self, instance):
if instance.pk:
return self.through.objects.filter(**self.through.lookup_kwargs(instance))
return self.through.objects.none()
def related_query_name(self):
return _model_name(self.model)
def m2m_reverse_name(self):
return _get_field(self.through, 'tag').column
def m2m_reverse_field_name(self):
return _get_field(self.through, 'tag').name
def m2m_target_field_name(self):
return self.model._meta.pk.name
def m2m_reverse_target_field_name(self):
# rel.to renamed to remote_field.model in Django 1.9
if VERSION >= (1, 9):
return self.remote_field.model._meta.pk.name
else:
return self.rel.to._meta.pk.name
def m2m_column_name(self):
if self.use_gfk:
return self.through._meta.virtual_fields[0].fk_field
return self.through._meta.get_field('content_object').column
def db_type(self, connection=None):
return None
def m2m_db_table(self):
return self.through._meta.db_table
def bulk_related_objects(self, new_objs, using):
return []
def extra_filters(self, pieces, pos, negate):
if negate or not self.use_gfk:
return []
prefix = "__".join(["tagged_items"] + pieces[:pos - 2])
get = ContentType.objects.get_for_model
cts = [get(obj) for obj in _get_subclasses(self.model)]
if len(cts) == 1:
return [("%s__content_type" % prefix, cts[0])]
return [("%s__content_type__in" % prefix, cts)]
def get_extra_join_sql(self, connection, qn, lhs_alias, rhs_alias):
model_name = _model_name(self.through)
if rhs_alias == '%s_%s' % (self.through._meta.app_label, model_name):
alias_to_join = rhs_alias
else:
alias_to_join = lhs_alias
extra_col = _get_field(self.through, 'content_type').column
content_type_ids = [ContentType.objects.get_for_model(subclass).pk for
subclass in _get_subclasses(self.model)]
if len(content_type_ids) == 1:
content_type_id = content_type_ids[0]
extra_where = " AND %s.%s = %%s" % (qn(alias_to_join),
qn(extra_col))
params = [content_type_id]
else:
extra_where = " AND %s.%s IN (%s)" % (qn(alias_to_join),
qn(extra_col),
','.join(['%s'] *
len(content_type_ids)))
params = content_type_ids
return extra_where, params
# This and all the methods till the end of class are only used in django >= 1.6
def _get_mm_case_path_info(self, direct=False):
pathinfos = []
linkfield1 = _get_field(self.through, 'content_object')
linkfield2 = _get_field(self.through, self.m2m_reverse_field_name())
if direct:
join1infos = linkfield1.get_reverse_path_info()
join2infos = linkfield2.get_path_info()
else:
join1infos = linkfield2.get_reverse_path_info()
join2infos = linkfield1.get_path_info()
pathinfos.extend(join1infos)
pathinfos.extend(join2infos)
return pathinfos
def _get_gfk_case_path_info(self, direct=False):
pathinfos = []
from_field = self.model._meta.pk
opts = self.through._meta
linkfield = _get_field(self.through, self.m2m_reverse_field_name())
if direct:
join1infos = [PathInfo(self.model._meta, opts, [from_field], _remote_field(self), True, False)]
join2infos = linkfield.get_path_info()
else:
join1infos = linkfield.get_reverse_path_info()
join2infos = [PathInfo(opts, self.model._meta, [from_field], self, True, False)]
pathinfos.extend(join1infos)
pathinfos.extend(join2infos)
return pathinfos
def get_path_info(self):
if self.use_gfk:
return self._get_gfk_case_path_info(direct=True)
else:
return self._get_mm_case_path_info(direct=True)
def get_reverse_path_info(self):
if self.use_gfk:
return self._get_gfk_case_path_info(direct=False)
else:
return self._get_mm_case_path_info(direct=False)
def get_joining_columns(self, reverse_join=False):
if reverse_join:
return ((self.model._meta.pk.column, "object_id"),)
else:
return (("object_id", self.model._meta.pk.column),)
def get_extra_restriction(self, where_class, alias, related_alias):
extra_col = _get_field(self.through, 'content_type').column
content_type_ids = [ContentType.objects.get_for_model(subclass).pk
for subclass in _get_subclasses(self.model)]
return ExtraJoinRestriction(related_alias, extra_col, content_type_ids)
def get_reverse_joining_columns(self):
return self.get_joining_columns(reverse_join=True)
#property
def related_fields(self):
return [(_get_field(self.through, 'object_id'), self.model._meta.pk)]
#property
def foreign_related_fields(self):
return [self.related_fields[0][1]]
...
taggit.models.py:
...
#python_2_unicode_compatible
class TagBase(models.Model):
name = models.CharField(verbose_name=_('Name'), unique=True, max_length=100)
slug = models.SlugField(verbose_name=_('Slug'), unique=True, max_length=100)
def __str__(self):
return self.name
class Meta:
abstract = True
def save(self, *args, **kwargs):
if not self.pk and not self.slug:
self.slug = self.slugify(self.name)
from django.db import router
using = kwargs.get("using") or router.db_for_write(
type(self), instance=self)
# Make sure we write to the same db for all attempted writes,
# with a multi-master setup, theoretically we could try to
# write and rollback on different DBs
kwargs["using"] = using
# Be oportunistic and try to save the tag, this should work for
# most cases ;)
try:
with atomic(using=using):
res = super(TagBase, self).save(*args, **kwargs)
return res
except IntegrityError:
pass
# Now try to find existing slugs with similar names
slugs = set(
self.__class__._default_manager
.filter(slug__startswith=self.slug)
.values_list('slug', flat=True)
)
i = 1
while True:
slug = self.slugify(self.name, i)
if slug not in slugs:
self.slug = slug
# We purposely ignore concurrecny issues here for now.
# (That is, till we found a nice solution...)
return super(TagBase, self).save(*args, **kwargs)
i += 1
else:
return super(TagBase, self).save(*args, **kwargs)
def slugify(self, tag, i=None):
slug = default_slugify(unidecode(tag))
if i is not None:
slug += "_%d" % i
return slug
class Tag(TagBase):
class Meta:
verbose_name = _("Tag")
verbose_name_plural = _("Tags")
app_label = 'taggit'
...
That's just the way HTML works; it converts any whitespace - including newlines - to spaces. If you don't want any spaces you will need to put everything on the same line.
{{ x.name }}{% if not forloop.last %}, {% endif %}
Maybe you could try:
{{ x.name.strip }}
In models.py I have:
class User(modals.Model):
name = models.CharField(max_length=255)
image = models.ImageField(blank=True, null=True)
And in admin.py:
class UserAdmin(admin.ModelAdmin):
list_filter = ['image']
admin.site.register(User, UserAdmin)
I just want to filter Users by have image or not (null or empty string)
But django shows filter by image urls =)
Is there a way to make list_filter = ['image'] behave like boolean field?
Big thx for advices!
admin.py
class ImageListFilter(admin.SimpleListFilter):
title = _('Has photo')
parameter_name = 'has_photo'
def lookups(self, request, model_admin):
return (
('yes', _('Yes')),
('no', _('No')),
)
def queryset(self, request, queryset):
if self.value() == 'yes':
return queryset.filter(image__isnull=False).exclude(image='')
if self.value() == 'no':
return queryset.filter(Q(image__isnull=True) | Q(image__exact=''))
class UserAdmin(admin.ModelAdmin):
list_filter = [ImageListFilter]
As of Django 3.1, you can use EmptyFieldListFilter as follows:
list_filter = (
("my_fk_field", admin.EmptyFieldListFilter),
)
See the docs for details. See the release notes as well.
The code is available here, if you need to customize it or backport it.
Based on MaxCore's answer, I created customised class that I can use to modify title and parameter name:
from django.contrib import admin
from django.utils.translation import ugettext_lazy as _
from django.db.models import Q
class NotNullFilter(admin.SimpleListFilter):
title = _('Filter title not set')
parameter_name = 'parameter name not set'
def lookups(self, request, model_admin):
return (
('not_null', _('Not empty only')),
('null', _('Empty only')),
)
def queryset(self, request, queryset):
if self.value() == 'not_null':
is_null_false = {
self.parameter_name + "__isnull": False
}
exclude = {
self.parameter_name: ""
}
return queryset.filter(**is_null_false).exclude(**exclude)
if self.value() == 'null':
is_null_true = {
self.parameter_name + "__isnull": True
}
param_exact = {
self.parameter_name + "__exact": ""
}
return queryset.filter(Q(**is_null_true) | Q(**param_exact))
class YoutubeNotNullFilter(NotNullFilter):
title = "Youtube"
parameter_name = "youtube_videoid"
K.H.'s approach of a base class that could be reused easily was really helpful for me. I couldn't get the written example to work, but with a small tweak it worked perfectly (Python 2.7, Django 1.10) to achieve this.
from django.contrib import admin
class NotNullFilter(admin.SimpleListFilter):
title = 'Filter title not set'
parameter_name = 'parameter name not set'
def lookups(self, request, model_admin):
return (
('not_null', 'Not empty only'),
('null', 'Empty only'),
)
def queryset(self, request, queryset):
filter_string = self.parameter_name + '__isnull'
if self.value() == 'not_null':
is_null_false = {
filter_string: False
}
return queryset.filter(**is_null_false)
if self.value() == 'null':
is_null_true = {
filter_string: True
}
return queryset.filter(**is_null_true)
class YoutubeNotNullFilter(NotNullFilter):
title = "Youtube"
parameter_name = "youtube_videoid"
class SomeVideoClass(admin.ModelAdmin):
...
list_filter = [YouTubeNotNullFilter, ...]
A bit more flexible version based on MaxCore's answer, which creates new classes on-the-fly
def by_null_filter(attr, name, null_label='is Null', non_null_label='not Null', bool_dt=False, bool_value=False):
class ByNullFilter(admin.SimpleListFilter):
"""List display filter to show null/not null values"""
parameter_name = attr
title = name
def lookups(self, request, model_admin):
if bool_dt:
label_null = 'not %s' % attr
label_non_null = attr
elif bool_value:
label_null = 'no'
label_non_null = 'yes'
else:
label_null = null_label
label_non_null = non_null_label
return (
('not_null', label_non_null),
('null', label_null)
)
def queryset(self, request, queryset):
filter_string = attr + '__isnull'
if self.value() == 'not_null':
is_null_false = {
filter_string: False
}
return queryset.filter(**is_null_false)
if self.value() == 'null':
is_null_true = {
filter_string: True
}
return queryset.filter(**is_null_true)
return ByNullFilter
Usage
if you have processed DateTime field and you want to filter on it - does it have value or null
# with default filter labels (`not Null`, `is Null`)
list_filter = (by_null_filter('processed', 'Processed'), )
# Processed header with labels based on field name (`processed`, `not processed`)
list_filter = (by_null_filter('processed', 'Processed', bool_dt=True), )
# Paid header filter with labels based on bool(end_time) (`yes`, `no`)
list_filter = (by_null_filter('end_time', 'Paid', bool_value=True), )
I have an admin model with a few inline models included with it (see the ResourceUserAdmin model below for full class):
inlines = [
ResourceLocationInlineAdmin ,
ResourceCategoryInlineAdmin ,
ResourceStageInlineAdmin ,
]
When a user clicks to create a new ResourceUserAdmin I want the inlines of the class ResourceCategoryInlineAdmin to get initial values -- note that these relationships will not be saved to the database. I've tried to override parts of the add_view function to get what i want but I can't figure out how to pass multiple inline forms back to parent.
Any ideas on how to achieve this?
Models
class ResourceUserAdmin( admin.ModelAdmin ):
inlines = [
ResourceLocationInlineAdmin ,
ResourceCategoryInlineAdmin ,
ResourceStageInlineAdmin ,
]
list_display = (
'user' ,
'name' ,
'state' ,
'email' ,
'website' ,
'phone' ,
'logo_url_link',
)
search_fields = ( 'name' , 'email' , 'website' )
list_filter = ( 'name' , 'state' , 'email' , 'website' )
ordering = ( 'name', )
fields = (
'user' ,
'name' ,
'state' ,
'email' ,
'website' ,
'phone' ,
'logo' ,
'ideal_candidate',
)
admin.site.register( ResourceUser, ResourceUserAdmin )
Here is the inline model I want to create many by default:
class ResourceCategoryInlineAdmin( admin.StackedInline ):
model = ResourceCategory
extra = 0
class ResourceCategoryAdmin( admin.ModelAdmin ):
list_display = ( 'user' ,
'category' , )
ordering = ( 'user' , )
fields = ( 'user' , 'category' )
def formfield_for_foreignkey( self, db_field, *args, **kwargs ):
if isinstance( db_field, models.ForeignKey ):
if db_field.name == 'category':
kwargs['widget'] = forms.RadioSelect()
return super( ResourceCategoryAdmin, self).formfield_for_foreignkey( db_field, **kwargs )
admin.site.register( ResourceCategory, ResourceCategoryAdmin )
This is one solution, though not the option i really wanted to use. You can do it by overriding the the whole add_view function and setting the intial attributes for each form in the formsets (look for the override comment below)
def add_view(self, request, form_url='', extra_context=None):
"The 'add' admin view for this model."
model = self.model
opts = model._meta
if not self.has_add_permission(request):
raise PermissionDenied
ModelForm = self.get_form(request)
formsets = []
if request.method == 'POST':
form = ModelForm(request.POST, request.FILES)
if form.is_valid():
new_object = self.save_form(request, form, change=False)
form_validated = True
else:
form_validated = False
new_object = self.model()
prefixes = {}
for FormSet, inline in zip(self.get_formsets(request), self.inline_instances):
prefix = FormSet.get_default_prefix()
prefixes[prefix] = prefixes.get(prefix, 0) + 1
if prefixes[prefix] != 1:
prefix = "%s-%s" % (prefix, prefixes[prefix])
formset = FormSet(data=request.POST, files=request.FILES,
instance=new_object,
save_as_new="_saveasnew" in request.POST,
prefix=prefix, queryset=inline.queryset(request))
formsets.append(formset)
if all_valid(formsets) and form_validated:
self.save_model(request, new_object, form, change=False)
form.save_m2m()
for formset in formsets:
self.save_formset(request, form, formset, change=False)
self.log_addition(request, new_object)
return self.response_add(request, new_object)
else:
# Prepare the dict of initial data from the request.
# We have to special-case M2Ms as a list of comma-separated PKs.
initial = dict(request.GET.items())
for k in initial:
logger.info( "for k in initial, k = %s" % k )
try:
f = opts.get_field(k)
except models.FieldDoesNotExist:
continue
if isinstance(f, models.ManyToManyField):
initial[k] = initial[k].split(",")
form = ModelForm(initial=initial)
prefixes = {}
for FormSet, inline in zip(self.get_formsets(request),
self.inline_instances):
prefix = FormSet.get_default_prefix()
prefixes[prefix] = prefixes.get(prefix, 0) + 1
if prefixes[prefix] != 1:
prefix = "%s-%s" % (prefix, prefixes[prefix])
formset = FormSet(instance=self.model(), prefix=prefix, queryset=inline.queryset(request))
#
#
# override the inlines of my choice
# to create initial values
#
#
if inline.__class__.__name__ == 'ResourceCategoryInlineAdmin':
for frm, category in zip( formset, Category.objects.all() ):
frm.fields['category'].initial = category
formsets.append(formset)
adminForm = helpers.AdminForm(form, list(self.get_fieldsets(request)),
self.prepopulated_fields, self.get_readonly_fields(request),
model_admin=self)
media = self.media + adminForm.media
inline_admin_formsets = []
for inline, formset in zip(self.inline_instances, formsets):
fieldsets = list(inline.get_fieldsets(request))
readonly = list(inline.get_readonly_fields(request))
inline_admin_formset = helpers.InlineAdminFormSet(inline, formset,
fieldsets, readonly, model_admin=self)
inline_admin_formsets.append(inline_admin_formset)
media = media + inline_admin_formset.media
context = {
'title': _('Add %s') % force_unicode(opts.verbose_name),
'adminform': adminForm,
'is_popup': "_popup" in request.REQUEST,
'show_delete': False,
'media': mark_safe(media),
'inline_admin_formsets': inline_admin_formsets,
'errors': helpers.AdminErrorList(form, formsets),
'root_path': self.admin_site.root_path,
'app_label': opts.app_label,
}
context.update(extra_context or {})
return self.render_change_form(request, context, form_url=form_url, add=True)
#return super(ResourceUserAdmin, self).add_view(request, form_url=form_url, extra_context=extra_context)