urls template httpresponseredirect examples example django image caching django-models

template - httpresponseredirect django example



Django: Al guardar, ¿cómo puedes verificar si un campo ha cambiado? (22)

En mi modelo tengo:

class Alias(MyBaseModel): remote_image = models.URLField(max_length=500, null=True, help_text="A URL that is downloaded and cached for the image. Only used when the alias is made") image = models.ImageField(upload_to=''alias'', default=''alias-default.png'', help_text="An image representing the alias") def save(self, *args, **kw): if (not self.image or self.image.name == ''alias-default.png'') and self.remote_image : try : data = utils.fetch(self.remote_image) image = StringIO.StringIO(data) image = Image.open(image) buf = StringIO.StringIO() image.save(buf, format=''PNG'') self.image.save(hashlib.md5(self.string_id).hexdigest() + ".png", ContentFile(buf.getvalue())) except IOError : pass

Lo que funciona muy bien por primera vez, el cambio de remote_image .

¿Cómo puedo obtener una nueva imagen cuando alguien ha modificado la remote_image en el alias? Y en segundo lugar, ¿hay una mejor manera de almacenar en caché una imagen remota?


¿Qué hay de usar la solución de David Cramer?

http://cramer.io/2010/12/06/tracking-changes-to-fields-in-django/

He tenido éxito al usarlo así:

@track_data(''name'') class Mode(models.Model): name = models.CharField(max_length=5) mode = models.CharField(max_length=5) def save(self, *args, **kwargs): if self.has_changed(''name''): print ''name changed'' # OR # @classmethod def post_save(cls, sender, instance, created, **kwargs): if instance.has_changed(''name''): print "Hooray!"



Aquí hay otra forma de hacerlo.

class Parameter(models.Model): def __init__(self, *args, **kwargs): super(Parameter, self).__init__(*args, **kwargs) self.__original_value = self.value def clean(self,*args,**kwargs): if self.__original_value == self.value: print("igual") else: print("distinto") def save(self,*args,**kwargs): self.full_clean() return super(Parameter, self).save(*args, **kwargs) self.__original_value = self.value key = models.CharField(max_length=24, db_index=True, unique=True) value = models.CharField(max_length=128)

Según la documentación: validación de objetos.

"El segundo paso que realiza full_clean () es llamar a Model.clean (). Este método debe anularse para realizar una validación personalizada en su modelo. Este método debe usarse para proporcionar una validación de modelo personalizada y, si lo desea, modificar los atributos de su modelo. Por ejemplo, podría usarlo para proporcionar automáticamente un valor para un campo, o para hacer una validación que requiera acceso a más de un solo campo: "


Aunque es un poco tarde, permítanme lanzar esta solución para otros que se encuentren en esta publicación. Esencialmente, desea anular el método __init__ de los models.Model para mantener una copia del valor original. Esto lo hace para que no tenga que hacer otra búsqueda de base de datos (que siempre es una buena cosa).

class Person(models.Model): name = models.CharField() __original_name = None def __init__(self, *args, **kwargs): super(Person, self).__init__(*args, **kwargs) self.__original_name = self.name def save(self, force_insert=False, force_update=False, *args, **kwargs): if self.name != self.__original_name: # name changed - do something here super(Person, self).save(force_insert, force_update, *args, **kwargs) self.__original_name = self.name


Desde el lanzamiento de Django 1.8, puede usar from_db classmethod para almacenar en caché el valor antiguo de remote_image. Luego, en el método de guardar , puede comparar el valor antiguo y el nuevo del campo para verificar si el valor ha cambiado.

@classmethod def from_db(cls, db, field_names, values): new = super(Alias, cls).from_db(db, field_names, values) # cache value went from the base new._loaded_remote_image = values[field_names.index(''remote_image'')] return new def save(self, force_insert=False, force_update=False, using=None, update_fields=None): if (self._state.adding and self.remote_image) or / (not self._state.adding and self._loaded_remote_image != self.remote_image): # If it is first save and there is no cached remote_image but there is new one, # or the value of remote_image has changed - do your stuff!


El mixin de @ivanlivski es genial.

Lo he extendido a

  • Asegúrate de que funcione con campos decimales.
  • Expone propiedades para simplificar el uso.

El código actualizado está disponible aquí: https://github.com/sknutsonsf/python-contrib/blob/master/src/django/utils/ModelDiffMixin.py

Para ayudar a las personas nuevas en Python o Django, daré un ejemplo más completo. Este uso particular es tomar un archivo de un proveedor de datos y asegurar que los registros en la base de datos reflejen el archivo.

Mi objeto modelo:

class Station(ModelDiffMixin.ModelDiffMixin, models.Model): station_name = models.CharField(max_length=200) nearby_city = models.CharField(max_length=200) precipitation = models.DecimalField(max_digits=5, decimal_places=2) # <list of many other fields> def is_float_changed (self,v1, v2): '''''' Compare two floating values to just two digit precision Override Default precision is 5 digits '''''' return abs (round (v1 - v2, 2)) > 0.01

La clase que carga el archivo tiene estos métodos:

class UpdateWeather (object) # other methods omitted def update_stations (self, filename): # read all existing data all_stations = models.Station.objects.all() self._existing_stations = {} # insert into a collection for referencing while we check if data exists for stn in all_stations.iterator(): self._existing_stations[stn.id] = stn # read the file. result is array of objects in known column order data = read_tabbed_file(filename) # iterate rows from file and insert or update where needed for rownum in range(sh.nrows): self._update_row(sh.row(rownum)); # now anything remaining in the collection is no longer active # since it was not found in the newest file # for now, delete that record # there should never be any of these if the file was created properly for stn in self._existing_stations.values(): stn.delete() self._num_deleted = self._num_deleted+1 def _update_row (self, rowdata): stnid = int(rowdata[0].value) name = rowdata[1].value.strip() # skip the blank names where data source has ids with no data today if len(name) < 1: return # fetch rest of fields and do sanity test nearby_city = rowdata[2].value.strip() precip = rowdata[3].value if stnid in self._existing_stations: stn = self._existing_stations[stnid] del self._existing_stations[stnid] is_update = True; else: stn = models.Station() is_update = False; # object is new or old, don''t care here stn.id = stnid stn.station_name = name; stn.nearby_city = nearby_city stn.precipitation = precip # many other fields updated from the file if is_update == True: # we use a model mixin to simplify detection of changes # at the cost of extra memory to store the objects if stn.has_changed == True: self._num_updated = self._num_updated + 1; stn.save(); else: self._num_created = self._num_created + 1; stn.save()


Esto me funciona en Django 1.8

def clean(self): if self.cleaned_data[''name''] != self.initial[''name'']: # Do something


He extendido la mezcla de @livskiy de la siguiente manera:

class ModelDiffMixin(models.Model): """ A model mixin that tracks model fields'' values and provide some useful api to know what fields have been changed. """ _dict = DictField(editable=False) def __init__(self, *args, **kwargs): super(ModelDiffMixin, self).__init__(*args, **kwargs) self._initial = self._dict @property def diff(self): d1 = self._initial d2 = self._dict diffs = [(k, (v, d2[k])) for k, v in d1.items() if v != d2[k]] return dict(diffs) @property def has_changed(self): return bool(self.diff) @property def changed_fields(self): return self.diff.keys() def get_field_diff(self, field_name): """ Returns a diff for field if it''s changed and None otherwise. """ return self.diff.get(field_name, None) def save(self, *args, **kwargs): """ Saves model and set initial state. """ object_dict = model_to_dict(self, fields=[field.name for field in self._meta.fields]) for field in object_dict: # for FileFields if issubclass(object_dict[field].__class__, FieldFile): try: object_dict[field] = object_dict[field].path except : object_dict[field] = object_dict[field].name # TODO: add other non-serializable field types self._dict = object_dict super(ModelDiffMixin, self).save(*args, **kwargs) class Meta: abstract = True

y el DictField es:

class DictField(models.TextField): __metaclass__ = models.SubfieldBase description = "Stores a python dict" def __init__(self, *args, **kwargs): super(DictField, self).__init__(*args, **kwargs) def to_python(self, value): if not value: value = {} if isinstance(value, dict): return value return json.loads(value) def get_prep_value(self, value): if value is None: return value return json.dumps(value) def value_to_string(self, obj): value = self._get_val_from_obj(obj) return self.get_db_prep_value(value)

se puede usar extendiéndolo en sus modelos; se agregará un campo _dict cuando sincronice / migre y ese campo almacenará el estado de sus objetos


La mejor manera es con una señal de pre_save . Puede que no haya sido una opción en el 2009 cuando se hizo y contestó esta pregunta, pero cualquiera que vea esto hoy debería hacerlo de esta manera:

@receiver(pre_save, sender=MyModel) def do_something_if_changed(sender, instance, **kwargs): try: obj = sender.objects.get(pk=instance.pk) except sender.DoesNotExist: pass # Object is new, so field hasn''t technically changed, but you may want to do something else here. else: if not obj.some_field == instance.some_field: # Field has changed # do something


La solución óptima es probablemente una que no incluya una operación de lectura de base de datos adicional antes de guardar la instancia del modelo, ni ninguna otra biblioteca de django. Es por esto que las soluciones de laffuste son preferibles. En el contexto de un sitio de administración, uno simplemente puede anular el método save_model, e invocar el método has_changed del formulario allí, como en la respuesta anterior de Sion. Llegas a algo como esto, dibujando en la configuración de ejemplo de Sion pero usando "changed_data" para obtener todos los cambios posibles:

class ModelAdmin(admin.ModelAdmin): fields=[''name'',''mode''] def save_model(self, request, obj, form, change): form.changed_data #output could be [''name''] #do somethin the changed name value... #call the super method super(self,ModelAdmin).save_model(request, obj, form, change)

  • Sobrescribir save_model:

https://docs.djangoproject.com/en/1.10/ref/contrib/admin/#django.contrib.admin.ModelAdmin.save_model

  • Método modificado de datos_datos para un campo:

https://docs.djangoproject.com/en/1.10/ref/forms/api/#django.forms.Form.changed_data



Mejorando la respuesta de @josh para todos los campos:

class Person(models.Model): name = models.CharField() def __init__(self, *args, **kwargs): super(Person, self).__init__(*args, **kwargs) self._original_fields = dict([(field.attname, getattr(self, field.attname)) for field in self._meta.local_fields if not isinstance(field, models.ForeignKey)]) def save(self, *args, **kwargs): if self.id: for field in self._meta.local_fields: if not isinstance(field, models.ForeignKey) and/ self._original_fields[field.name] != getattr(self, field.name): # Do Something super(Person, self).save(*args, **kwargs)

solo para aclarar, getattr trabaja para obtener campos como person.name con cadenas (es decir, getattr(person, "name")


Otra respuesta tardía, pero si solo está intentando ver si se ha cargado un nuevo archivo en un campo de archivo, intente esto: (adaptado del comentario de Christopher Adams en el enlace http://zmsmith.com/2010/05/django-check-if-a-field-has-changed/ en el comentario de zach aquí

Enlace actualizado: https://web.archive.org/web/20130101010327/http://zmsmith.com:80/2010/05/django-check-if-a-field-has-changed/

def save(self, *args, **kw): from django.core.files.uploadedfile import UploadedFile if hasattr(self.image, ''file'') and isinstance(self.image.file, UploadedFile) : # Handle FileFields as special cases, because the uploaded filename could be # the same as the filename that''s already there even though there may # be different file contents. # if a file was just uploaded, the storage model with be UploadedFile # Do new file stuff here pass


Puede usar django-model-changes para hacer esto sin una búsqueda de base de datos adicional:

from django.dispatch import receiver from django_model_changes import ChangesMixin class Alias(ChangesMixin, MyBaseModel): # your model @receiver(pre_save, sender=Alias) def do_something_if_changed(sender, instance, **kwargs): if ''remote_image'' in instance.changes(): # do something


Si bien esto no responde realmente a tu pregunta, trataría esto de una manera diferente.

Simplemente borre el campo remote_image después de guardar con éxito la copia local. Luego, en su método de guardar, siempre puede actualizar la imagen siempre que remote_image no esté vacío.

Si desea mantener una referencia a la url, puede usar un campo booleano no editable para manejar la bandera de almacenamiento en caché en lugar del campo remote_image .


Si está utilizando un formulario, puede usar los datos modificados de Form ( docs ):

class AliasForm(ModelForm): def save(self, commit=True): if ''remote_image'' in self.changed_data: # do things remote_image = self.cleaned_data[''remote_image''] do_things(remote_image) super(AliasForm, self).save(commit) class Meta: model = Alias



Tuve esta situación antes de que mi solución fuera anular el método pre_save() de la clase de campo de destino, se llamará solo si el campo ha sido cambiado
útil con el ejemplo FileField:

class PDFField(FileField): def pre_save(self, model_instance, add): # do some operations on your file # if and only if you have changed the filefield

desventaja:
no es útil si desea realizar cualquier operación (post_save) como usar el objeto creado en algún trabajo (si ha cambiado cierto campo)


Una modificación a la respuesta de @ ivanperelivskiy:

@property def _dict(self): ret = {} for field in self._meta.get_fields(): if isinstance(field, ForeignObjectRel): # foreign objects might not have corresponding objects in the database. if hasattr(self, field.get_accessor_name()): ret[field.get_accessor_name()] = getattr(self, field.get_accessor_name()) else: ret[field.get_accessor_name()] = None else: ret[field.attname] = getattr(self, field.attname) return ret

Esto usa el método público get_fields django 1.10 en su lugar. Esto hace que el código sea más seguro en el futuro, pero lo más importante es que también incluye claves externas y campos donde editable = False.

Para referencia, aquí está la implementación de .fields

@cached_property def fields(self): """ Returns a list of all forward fields on the model and its parents, excluding ManyToManyFields. Private API intended only to be used by Django itself; get_fields() combined with filtering of field properties is the public API for obtaining this field list. """ # For legacy reasons, the fields property should only contain forward # fields that are not private or with a m2m cardinality. Therefore we # pass these three filters as filters to the generator. # The third lambda is a longwinded way of checking f.related_model - we don''t # use that property directly because related_model is a cached property, # and all the models may not have been loaded yet; we don''t want to cache # the string reference to the related_model. def is_not_an_m2m_field(f): return not (f.is_relation and f.many_to_many) def is_not_a_generic_relation(f): return not (f.is_relation and f.one_to_many) def is_not_a_generic_foreign_key(f): return not ( f.is_relation and f.many_to_one and not (hasattr(f.remote_field, ''model'') and f.remote_field.model) ) return make_immutable_fields_list( "fields", (f for f in self._get_fields(reverse=False) if is_not_an_m2m_field(f) and is_not_a_generic_relation(f) and is_not_a_generic_foreign_key(f)) )


Y ahora, para una respuesta directa: una forma de verificar si el valor del campo ha cambiado es obtener datos originales de la base de datos antes de guardar la instancia. Considera este ejemplo:

class MyModel(models.Model): f1 = models.CharField(max_length=1) def save(self, *args, **kw): if self.pk is not None: orig = MyModel.objects.get(pk=self.pk) if orig.f1 != self.f1: print ''f1 changed'' super(MyModel, self).save(*args, **kw)

Lo mismo se aplica cuando se trabaja con un formulario. Puede detectarlo en el método de limpieza o guardado de un ModelForm:

class MyModelForm(forms.ModelForm): def clean(self): cleaned_data = super(ProjectForm, self).clean() #if self.has_changed(): # new instance or existing updated (form has data to save) if self.instance.pk is not None: # new instance only if self.instance.f1 != cleaned_data[''f1'']: print ''f1 changed'' return cleaned_data class Meta: model = MyModel exclude = []


Yo uso el siguiente mixin:

from django.forms.models import model_to_dict class ModelDiffMixin(object): """ A model mixin that tracks model fields'' values and provide some useful api to know what fields have been changed. """ def __init__(self, *args, **kwargs): super(ModelDiffMixin, self).__init__(*args, **kwargs) self.__initial = self._dict @property def diff(self): d1 = self.__initial d2 = self._dict diffs = [(k, (v, d2[k])) for k, v in d1.items() if v != d2[k]] return dict(diffs) @property def has_changed(self): return bool(self.diff) @property def changed_fields(self): return self.diff.keys() def get_field_diff(self, field_name): """ Returns a diff for field if it''s changed and None otherwise. """ return self.diff.get(field_name, None) def save(self, *args, **kwargs): """ Saves model and set initial state. """ super(ModelDiffMixin, self).save(*args, **kwargs) self.__initial = self._dict @property def _dict(self): return model_to_dict(self, fields=[field.name for field in self._meta.fields])

Uso:

>>> p = Place() >>> p.has_changed False >>> p.changed_fields [] >>> p.rank = 42 >>> p.has_changed True >>> p.changed_fields [''rank''] >>> p.diff {''rank'': (0, 42)} >>> p.categories = [1, 3, 5] >>> p.diff {''categories'': (None, [1, 3, 5]), ''rank'': (0, 42)} >>> p.get_field_diff(''categories'') (None, [1, 3, 5]) >>> p.get_field_diff(''rank'') (0, 42) >>>

Nota

Tenga en cuenta que esta solución funciona bien solo en el contexto de la solicitud actual. Por lo tanto, es adecuado principalmente para casos simples. En un entorno concurrente donde varias solicitudes pueden manipular la misma instancia de modelo al mismo tiempo, definitivamente necesita un enfoque diferente.


como una extensión de la respuesta de SmileyChris, puede agregar un campo de fecha y hora al modelo para last_updated, y establecer algún tipo de límite para la edad máxima a la que podrá acceder antes de verificar un cambio