I am using django-simple-history to log changes in my models. I have managed to save log of changes, but I always get None on field history_user.
I am following this tutorial.
This is my model:
class Reservation(models.Model):
UNCONFIRMED = 'UNCONFIRMED'
CONFIRMED = 'CONFIRMED'
CANCELED = 'CANCELED'
NO_SHOW = 'NO SHOW'
GO_SHOW = 'GO SHOW'
STATUS_CHOICES = (
(UNCONFIRMED, _('Unconfirmed')),
(CONFIRMED, _('Confirmed')),
(CANCELED, _('Canceled')),
(NO_SHOW, _('No show')),
(GO_SHOW, _('Go show')),
)
booking = models.CharField(max_length=25, verbose_name=_('Booking'))
agency = models.ForeignKey(Agency, on_delete=models.PROTECT, related_name='reservations', verbose_name=_('Agency'))
comment = models.TextField(null=True, blank=True, verbose_name=_('Comment'))
status = models.CharField(max_length=15, choices=STATUS_CHOICES, default=UNCONFIRMED, verbose_name=_('Status'))
confirmation_date = models.DateTimeField(null=True, blank=True, verbose_name=_("Confirmation Date"))
arrival_date = models.DateField(verbose_name=_('Arrival Date'))
departure_date = models.DateField(verbose_name=_('Departure Date'))
confirmation = models.CharField(max_length=15, null=True, blank=True, verbose_name=_('Confirmation Code'))
is_invoiced = models.BooleanField(default=False, verbose_name=_('Is invoiced?'))
euroamerica = models.BooleanField(default=False, verbose_name=_("Is Euroamerica sale"))
user = models.ForeignKey(User, null=True, blank=True, on_delete=models.PROTECT, related_name='reservations')
changed_by = models.ForeignKey(User, null=True, blank=True, on_delete=models.PROTECT)
timestamp = models.DateTimeField(null=True, blank=True, auto_now_add=True)
handle_fee = models.FloatField(null=True, blank=True, verbose_name=_("Handle Fee"))
log = HistoricalRecords(related_name='history')
class Meta:
verbose_name = _('Reservation')
verbose_name_plural = _('Reservations')
permissions = (
('reservation_can_edit', 'Can Edit Reservation'),
('reservation_can_view', 'Can View Reservation'),
('reservation_can_view_history_log', 'Can View Reservation History Log')
)
def __str__(self):
return "[{}]{}".format(self.booking, self.agency)
def clean(self, *args, **kwargs):
if self.id is None:
try:
Reservation.objects.get(booking=self.booking)
except:
pass
else:
raise CustomValidation(_('Booking already exists.'), 'booking', status_code=status.HTTP_400_BAD_REQUEST)
if isinstance(self.arrival_date, str):
raise CustomValidation(_('Arrival date must be a valid date.'), 'arrival_date', status_code=status.HTTP_400_BAD_REQUEST)
if isinstance(self.departure_date, str):
raise CustomValidation(_('Departure date must be a valid date.'), 'departure_date', status_code=status.HTTP_400_BAD_REQUEST)
if self.arrival_date >= self.departure_date:
raise CustomValidation(_('Departure date must be later than Arrival date.'), 'departure_date', status_code=status.HTTP_400_BAD_REQUEST)
# elif self.arrival_date <= timezone.datetime.now().date():
# if self.id == None:
# raise CustomValidation(_('Arrival date must be later than today.'), 'arrival_date', status_code=status.HTTP_400_BAD_REQUEST)
if self.status == 'CONFIRMED' and self.confirmation is None:
raise CustomValidation(_('Must provide a confirmation number.'), 'confirmation', status_code=status.HTTP_400_BAD_REQUEST)
return True
def save(self, *args, **kwargs):
from GeneralApp.models import Parameter
self.booking = self.booking.replace(' ', '')
self.booking = self.booking.upper()
self.full_clean()
if self.confirmation is not None and self.status is not self.CONFIRMED:
if self.pk is not None:
try:
previous_reservation = Reservation.objects.get(id=self.pk)
except:
pass
else:
if previous_reservation.status == self.status and previous_reservation.confirmation != self.confirmation:
self.status = self.CONFIRMED
else:
self.status = self.CONFIRMED
if self.status == self.CONFIRMED and self.confirmation_date is None:
self.confirmation_date = timezone.now()
try:
self.handle_fee = Parameter.objects.get(name="Handle Fee").value
except:
self.handle_fee = None
super(Reservation, self).save(*args, **kwargs)
#property
def _history_user(self):
return self.changed_by
#_history_user.setter
def _history_user(self, value):
self.changed_by = value
I don't know what I am missing to get the user logged.
EDIT
Here is where I save Reservation:
def save_reservation(self, reservation_data, user, pk):
try:
reservation_to_save = models.Reservation.objects.get(booking=pk)
except:
raise CustomValidation(_("There is not such reservation {}".format(pk)), 'id', status.HTTP_400_BAD_REQUEST)
reservation_to_save.booking = reservation_data['booking']
reservation_to_save.agency = models.Agency.objects.get(id=reservation_data['agency'])
reservation_to_save.comment = reservation_data.get('comment', None)
reservation_to_save.status = reservation_data.get('status', 'UNCONFIRMED')
reservation_to_save.arrival_date = reservation_data['arrival_date']
reservation_to_save.departure_date = reservation_data['departure_date']
reservation_to_save.confirmation = reservation_data.get('confirmation', None)
reservation_to_save.is_invoiced = reservation_data.get('is_invoiced', False)
reservation_to_save.euroamerica = reservation_data.get('euroamerica', False)
reservation_to_save.save()
return reservation_to_save
According to the documentation you have to add HistoryRequestMiddleware to your middleware in settings.py
Like so:
MIDDLEWARE = [
# ...
'simple_history.middleware.HistoryRequestMiddleware',
]
This at least was the solution in my case.
If you don't want to use this middleware you can set the user manually, see this.
Related
So I have this code below that given a user_uuid I look up the info of a user. All request come with a auth token that contains the current user's uuid. If the current user queried for their own profile then the is_current_user key should be true, which is done with the item['is_current_user'] = user_uuid == request.user.uuid line and then I have another flag for if the current user is following the queried user which is item['is_following'] = FollowUser.objects.filter(follower=str(request.user.uuid), followee=user_uuid).exists(). As you can see these are very ugly hacks and I was wondering if I could add these values to the annotate function.
def get_user_info(request, user_uuid):
try:
query_annotated = User.objects.annotate(
follower_count=Count('followee_id', distinct=True),
followee_count=Count('follower_id', distinct=True)).get(pk=user_uuid)
except User.DoesNotExist as e:
return Response(dict(error=str(e),
user_message='User not found.'),
status=status.HTTP_404_NOT_FOUND)
serializer = FullUserSerializer(query_annotated)
# TODO: This is a hack the above annotation is not working
item = serializer.data
item['is_following'] = FollowUser.objects.filter(follower=str(request.user.uuid), followee=user_uuid).exists()
item['is_current_user'] = user_uuid == request.user.uuid
return Response(item, status=status.HTTP_200_OK)
Version 2
FullUserSerializer.py
class FullUserSerializer(serializers.ModelSerializer):
follower_count = serializers.IntegerField()
followee_count = serializers.IntegerField()
is_following = serializers.BooleanField()
is_current_user = serializers.BooleanField()
class Meta:
model = User
fields = ('uuid', 'username', 'avatar', 'created', 'follower_count',
'followee_count', 'is_current_user', 'is_following')
view.py
def get_user_info(request, user_uuid):
try:
query_annotated = User.objects.annotate(
follower_count=Count('followee_id', distinct=True),
followee_count=Count('follower_id', distinct=True),
is_following=Exists(
FollowUser.objects.filter(
follower=str(request.user.uuid),
followee=OuterRef("pk")
)
),
is_current_user=Case(
When(
pk=str(request.user.uuid),
then=Value(True)
),
default=Value(False),
output_field=BooleanField(),
),
).get(pk=user_uuid),
except User.DoesNotExist as e:
return Response(dict(error=str(e),
user_message='User not found.'),
status=status.HTTP_404_NOT_FOUND)
serializer = FullUserSerializer(query_annotated)
# TODO: This is a hack the above annotation is not working
# item = serializer.data
# item['is_following'] = FollowUser.objects.filter(follower=str(request.user.uuid), followee=user_uuid).exists()
# item['is_current_user'] = user_uuid == request.user.uuid
return Response(serializer.data, status=status.HTTP_200_OK)
model.py
class User(AbstractDatesModel):
uuid = models.UUIDField(primary_key=True)
username = models.CharField(max_length=USERNAME_MAX_LEN, unique=True, validators=[
MinLengthValidator(USERNAME_MIN_LEN)])
created = models.DateTimeField('Created at', auto_now_add=True)
updated_at = models.DateTimeField('Last updated at', auto_now=True, blank=True, null=True)
avatar = models.ImageField(upload_to=avatar_directory_path, blank=True, null=True)
#property
def avatar_url(self):
return self.avatar.url
Try this:
from django.db.models import Case, Exists, OuterRef, When, Value
query_annotated = User.objects.annotate(
# ...
is_following=Exists(
FollowUser.objects.filter(
follower=str(request.user.uuid),
followee=OuterRef("pk")
)
),
is_current_user=Case(
When(
pk=request.user.uuid,
then=Value(True)
),
default=Value(False),
output_field=BooleanField(),
),
).get(pk=user_uuid)
I'm doing a query to get the chocies from a charfield and assign them. But at the moment I want to save the form in my view I am getting this error. What I can do?
ValueError: Cannot assign "'17'": "Con_Transaccioncab.doc" must be a "Adm_Documento" instance.
In the form I retrieve and assign the options to the field
form.py
class Con_MovimientosForm(ModelForm):
class Meta:
model = Con_Transaccioncab
fields = '__all__'
exclude = ['cab_estado']
widgets = {
'emp_id': HiddenInput(),
'per_id': HiddenInput(),
'suc_id': HiddenInput(),
'cab_numero': HiddenInput(),
'cab_estadocon': RadioSelect,
'cab_escambpat': CheckboxInput,
'cab_fecha': DatePickerInput(format=Form_CSS.fields_date_format,
options=Form_CSS.fields_date_opts,
attrs={'value': Form_CSS.fields_current_date}),
}
def __init__(self, *args, **kwargs):
self.AIGN_EMP_ID = kwargs.pop("AIGN_EMP_ID")
self.AIGN_PER_ID = kwargs.pop("AIGN_PER_ID")
# --------------------------------------------------------------------------------------
self.AIGN_OPCIONES = kwargs.pop("AIGN_OPCIONES")
self.PERMISOS = [] # para recuperar los permisos de la tabla
__json_values = json.loads(json.dumps(self.AIGN_OPCIONES))
self.PERMISOS = recuperarPermisos(__json_values, self._meta.model.get_table_name(self._meta.model))
# --------------------------------------------------------------------------------------
super(Con_MovimientosForm, self).__init__(*args, **kwargs)
self.fields['cab_estadocon'].required = False
self.fields['cab_numero'].required = False
# here I am assigned the choices to the field
self.fields['doc'] = ChoiceField(label='Acción: ', choices=self.get_choices(), required=False)
for form in self.visible_fields():
form.field.widget.attrs['placeholder'] = Form_CSS.fields_placeholder + form.field.label.lower()
form.field.widget.attrs['autocomplete'] = Form_CSS.fields_autocomplete
form.field.widget.attrs['class'] = Form_CSS.fields_attr_class
self.helper = FormHelper(self)
self.helper.form_method = 'post'
self.helper.form_id = Form_CSS.getFormID(self)
self.helper.attrs = Form_CSS.form_attrs
self.helper.form_tag = True
self.helper.form_error_title = Form_CSS.form_err_title
self.helper.form_class = Form_CSS.form_class
self.helper.label_class = Form_CSS.fields_label_class
self.helper.field_class = 'col-sm-5'
self.helper.layout = Layout(
Div(
DivHeaderWithButtons(instance_pk=self.instance.pk, remove_delete=True, remove_print=False,
permisos=self.PERMISOS),
Div(
Div(
Div('cab_numero',
'cab_fecha',
'doc', # this is the field of which the error is giving
'cab_observacion',
Div('cab_estadocon', style='pointer-events: none;'),
'per_id',
'suc_id',
'emp_id',
css_class='col-sm'),
Div('cab_beneficia',
'cab_essaldoini',
'cab_escambpat',
'cab_elaborado',
'cab_revisado',
'cab_aprovado',
css_class='col-sm'),
css_class='row'
),
css_class='card-body', id='body_id'
),
css_class='card'
),
Div(
DivGridHeaderWithButtons(instance_pk=self.instance.pk,
grid_opts=get_MovimientosDetForm(self.AIGN_PER_ID, None)),
)
)
def clean(self):
super(Con_MovimientosForm, self).clean()
return self.cleaned_data
# This is the query where I get the choices
def get_choices(self):
all_tipoaux = Adm_DocumentoPeriodo.objects.select_related('doc').filter \
(per=self.AIGN_PER_ID).select_related('doc__mod').filter(doc__mod__mod_codigov='CON').values("doc__doc_id",
"doc__doc_nombre")
DOC = [(int(d['doc__doc_id']), str(d['doc__doc_nombre'])) for d in all_tipoaux]
return DOC
these are the 3 models that I am using for this example.
models.py
class Con_Transaccioncab(models.Model):
ESTADO_CHOICES = [
('S', 'Sin Contabilizar'),
('C', 'Contabilizado'),
('I', 'Incompleto'),
('A', 'Anulado'),
]
cab_id = models.BigAutoField(primary_key=True)
cab_numero = models.BigIntegerField(verbose_name='#')
cab_fecha = models.DateField(verbose_name='Fecha')
cab_estadocon = models.CharField(
max_length=2,
choices=ESTADO_CHOICES,
default="I", verbose_name="Estado")
cab_observacion = models.CharField(max_length=128, blank=True, null=True, verbose_name='Observación')
cab_elaborado = models.CharField(max_length=32, blank=True, null=True, verbose_name='Elaborado por')
cab_revisado = models.CharField(max_length=32, blank=True, null=True, verbose_name='Revisado por')
cab_aprovado = models.CharField(max_length=32, blank=True, null=True, verbose_name='Aprobado por')
cab_beneficia = models.CharField(max_length=32, blank=True, null=True, verbose_name='Beneficiario')
cab_essaldoini = models.BooleanField(verbose_name='Saldos iniciales',default=False)
cab_escambpat = models.BooleanField(verbose_name='Cambio patrimonio',default=False)
doc = models.ForeignKey(Adm_Documento, models.DO_NOTHING, db_column='doc_id', verbose_name="Documento")
per_id = models.ForeignKey(Adm_Periodo, models.DO_NOTHING, db_column='per_id')
suc_id = models.ForeignKey(Adm_Sucursal, models.DO_NOTHING, db_column='suc_id')
emp_id = models.ForeignKey(Adm_Empresa, models.DO_NOTHING, db_column='emp_id')
cab_estado = models.SmallIntegerField(default=True)
class Meta:
managed = False
verbose_name = 'Movimientos'
db_table = 'con\".\"transaccioncab'
unique_together = (('cab_id', 'doc_id', 'per_id', 'suc_id', 'emp_id'),)
class Adm_Documento(models.Model):
class Choices_DocTipo(models.TextChoices):
Ingreso = 'I', _('Ingreso')
Egreso = 'E', _('Egreso')
Transferencia = 'T', _('Transferencia')
EntregaPedido = 'P', _('Entrega pedido')
class Choices_DocMovDebCre(models.TextChoices):
Cheque = 'CHE', _('Cheque')
Deposito = 'DEP', _('Depósito')
NotaCredito = 'NDC', _('Transferencia')
NotaDebito = 'NDD', _('Entrega pedido')
Ninguno = '', _('Ninguno')
doc_id = models.BigAutoField(primary_key=True)
doc_codigov = models.CharField(max_length=32, verbose_name='Código')
doc_nombre = models.CharField(max_length=64, verbose_name='Nombre')
doc_tipo = models.CharField(max_length=1, blank=True, null=True, verbose_name='Tipo',
choices=Choices_DocTipo.choices)
doc_orden = models.SmallIntegerField(verbose_name='Orden')
doc_actcosto = models.BooleanField(verbose_name='Actualiza costo')
doc_movcre = models.CharField(max_length=3, blank=True, null=True, verbose_name='Mov. Crédito',
choices=Choices_DocMovDebCre.choices, default=Choices_DocMovDebCre.Ninguno)
doc_movdeb = models.CharField(max_length=3, blank=True, null=True, verbose_name='Mov. Débito',
choices=Choices_DocMovDebCre.choices, default=Choices_DocMovDebCre.Ninguno)
tcc = models.ForeignKey(Sis_TemplateContabCab, models.DO_NOTHING, blank=True, null=True,
verbose_name=Sis_TemplateContabCab._meta.verbose_name)
mod = models.ForeignKey(Sis_Modulo, models.DO_NOTHING, verbose_name=Sis_Modulo._meta.verbose_name)
emp = models.ForeignKey(Adm_Empresa, models.DO_NOTHING, verbose_name=Adm_Empresa._meta.verbose_name)
doc_estado = models.SmallIntegerField(default=True)
class Meta:
managed = False
db_table = 'adm\".\"documento'
unique_together = (('doc_id', 'emp'), ('doc_codigov', 'emp'))
verbose_name = 'Documento'
verbose_name_plural = 'Documentos'
def __str__(self):
return self.doc_nombre
class Adm_DocumentoPeriodo(models.Model):
dop_id = models.BigAutoField(primary_key=True)
doc = models.ForeignKey(Adm_Documento, models.DO_NOTHING,db_column='doc_id')
per = models.ForeignKey(Adm_Periodo, models.DO_NOTHING,db_column='per_id')
emp = models.ForeignKey(Adm_Empresa, models.DO_NOTHING,db_column='emp_id')
dop_secuencia = models.BigIntegerField(verbose_name='Secuencia')
dop_estado = models.SmallIntegerField(default=True)
class Meta:
managed = False
db_table = 'adm\".\"documento_periodo'
unique_together = (('per', 'emp', 'doc'),)
The error occurs in the view at form.is_valid()
View.py
class Con_MovimientosUpdateView(UpdateView):
model = Con_Transaccioncab
form_class = Con_MovimientosForm
template_name = 'con_movimientos/form.html'
success_url = reverse_lazy('con_man:con_movimientos_list')
error_url = reverse_lazy('con_man:con_movimientos_add')
def dispatch(self, request, *args, **kwargs):
for k in self.kwargs.keys():
self.kwargs[k] = _e.decrypt(self.kwargs[k])
self.object = self.get_object()
return super().dispatch(request, *args, **kwargs)
def get_form_kwargs(self):
kwargs = super(Con_MovimientosUpdateView, self).get_form_kwargs()
kwargs.update({'AIGN_EMP_ID': self.request.session['AIGN_EMP_ID']})
kwargs.update({'AIGN_PER_ID': self.request.session['AIGN_PER_ID']})
kwargs.update({'AIGN_OPCIONES': self.request.session['AIGN_OPCIONES']})
return kwargs
def post(self, request, *args, **kwargs):
r = {'a': 1}
extra_errors = []
form = self.get_form()
if 'SAVE' in request.POST and form.is_valid(): # In this line the error occurs
if 'cab_escambpat' in request.POST and request.POST['es_patri'] == 'false':
return JsonResponse({'__all__': ["Error debe existir almenos una cuenta de patrimonio"]})
else:
try:
df_movDetCuenta = pd.DataFrame(json.loads(request.POST['movimientos_grid']))
df_movDetCuenta["det_desc"].fillna('', inplace=True)
df_movDetCuenta["det_refdoc"].fillna('', inplace=True)
with transaction.atomic():
con_transaccioncab = self.get_object()
for index, row in df_movDetCuenta.iterrows():
if float(row.det_valor) > 0:
cueDet = Con_Transacciondet()
if not pd.isna(row.det_id):
cueDet.det_id = row.det_id
cueDet.det_estado = 0 if row._action == 'E' else 1
else:
cueDet.det_estado = 1
cueDet.cue_id_id = row.cue_id_id
cueDet.cab_id_id = con_transaccioncab.cab_id
cueDet.suc_id = con_transaccioncab.suc_id
cueDet.emp_id = con_transaccioncab.emp_id
cueDet.per_id = con_transaccioncab.per_id
cueDet.doc_id = con_transaccioncab.doc_id
cueDet.det_fecha = con_transaccioncab.cab_fecha
cueDet.det_desc = row.det_desc
cueDet.det_istransf = row.det_istransf
cueDet.det_refdoc = row.det_refdoc
cueDet.det_tipomov = row.det_tipomov
cueDet.det_valor = row.det_valor
cueDet.save()
form.save()
messages.success(request, CRUD_MSG.SAVE)
return JsonResponse(r)
except django.db.models.query_utils.InvalidQuery as e:
extra_errors.append(str(e))
self.object = None
context = self.get_context_data(**kwargs)
context['form'] = form
errors = form._errors.setdefault("__all__", ErrorList())
errors.extend(extra_errors)
return JsonResponse(r)
In Django, ForeigField references the object itself not its id. So calling it doc_id is incorrect and confusing. What you should do is call the field doc. Then setting doc_id would work as you'd expect.
With what you have now you'd need to use doc_id_id to set the actual id which is... you know...
I ask for help with the task.
There is a notification model. I want to create an asynchronous task for creating notifications. But I get an error Object of type MPTTModelBase is not JSON serializable.
models.py
class Comment(MPTTModel):
"""Модель комментариев"""
content_type = models.ForeignKey(ContentType, verbose_name=_('Тип контента'), related_name='content_ct_%(class)s', on_delete=models.CASCADE)
object_id = models.PositiveIntegerField(verbose_name=_('ID контента'), db_index=True)
content_object = GenericForeignKey('content_type', 'object_id')
"""Род.коммент"""
parent = TreeForeignKey('self', on_delete=models.CASCADE, verbose_name=_('Родительский комментарий'), blank=True,
null=True, related_name='children')
"""Инфо, привязка, модерация"""
content = models.TextField(verbose_name=_('Комментарий'))
created_by = models.ForeignKey(AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='comment_created_by', verbose_name=_('Автор комментария'))
is_published = models.BooleanField(verbose_name=_('Опубликовать'), default=True)
time_create = models.DateTimeField(auto_now_add=True, verbose_name=_('Дата добавления'))
"""Generic FK"""
rating = GenericRelation('Rating', related_query_name='%(class)s')
notification = GenericRelation('Notification', related_query_name='%(class)s')
def save(self, *args, **kwargs):
send_create_notification.delay(self, 3)
super().save(*args, **kwargs)
services.py
def create_notification(instance, type):
"""Notification create"""
from modules.core.models import Notification
model_object = instance
obj = model_object.content_object
try:
text = model_object.content[0:120]
except:
text = None
try:
to_user = obj.created_by
except:
to_user = obj
from_user = model_object.created_by
now = timezone.now()
last_minute = now - datetime.timedelta(seconds=60)
similar_actions = Notification.objects.filter(from_user=from_user, to_user=from_user, type=type, time_create__gte=last_minute)
if obj:
from django.contrib.contenttypes.models import ContentType
content_type = ContentType.objects.get_for_model(obj)
similar_actions = similar_actions.filter(content_type=content_type, object_id=obj.id)
if not similar_actions:
if text:
notification = Notification(from_user=from_user, to_user=to_user, type=type, content_object=obj, text=text)
else:
notification = Notification(from_user=from_user, to_user=to_user, type=type, content_object=obj)
notification.save()
return True
return False
tasks.py
#shared_task()
def send_create_notification(self, type):
return create_notification(self, type)
send_create_notification.delay(self, 3) will try to serialize your Comment instance, which is not JSON serializable. You could use a pickle serializerbut it's not recommended.
Instead I suggest you to send the comment id as an argument to the task:
send_create_notification.delay(self.pk, 3)
and get the instance on the task
#shared_task()
def send_create_notification(comment_id, type):
comment = Comment.objects.get(pk=comment_id)
return create_notification(comment, type)
I can't figure out why disconnect of post_save signal does not work in my project.
When I call PipedriveSync.objects.first().sync_with_pipedrive(), it does something and then tries to save some information to the object. Then, the sync_pipedrivesync receiver is called which calls sync_with_pipedrive() again which invokes sync_pipedrivesync etc etc.
To avoid this cycling I've created two methods - disconnect_sync and connect_sync which should disconnect signal when saving instance and then revoke it. But it doesn't work.
I can see in debugger that the last line in sync_with_pipedrive - self.save(disconnect=True) invokes the signal.
Do you know what is wrong?
models.py
class PipedriveSync(TimeStampedModel):
pipedrive_id = models.IntegerField(null=True, blank=True)
last_sync = models.DateTimeField(null=True, blank=True)
last_sync_response = JSONField(null=True, blank=True)
last_sync_success = models.NullBooleanField()
last_backsync = models.DateTimeField(null=True, blank=True)
last_backsync_response = JSONField(null=True, blank=True)
last_backsync_success = models.NullBooleanField()
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
def save(self,disconnect=True,**kwargs):
if disconnect:
PipedriveSync.disconnect_sync()
super().save(**kwargs)
PipedriveSync.connect_sync()
#classmethod
def disconnect_sync(cls):
post_save.disconnect(sync_pipedrivesync)
#classmethod
def connect_sync(cls):
post_save.connect(sync_pipedrivesync,sender=PipedriveSync)
def sync_with_pipedrive(self):
if self.pipedrive_id:
action = 'update'
else:
action = 'create'
relative_url = self.build_endpoint_relative_url(action)
payload = self.get_serializer_class()(self.content_object).data
response = None
if action == 'create':
response = PipeDriveWrapper.post(relative_url, payload=payload)
if response['success']:
self.pipedrive_id = response['data']['id']
if action == 'update':
response = PipeDriveWrapper.put(relative_url, payload=payload)
try:
success = response['success']
except KeyError:
success = False
self.last_sync_success = success
self.last_sync_response = response
self.last_sync = now()
self.save(disconnect=True)
#receiver(post_save, sender=PipedriveSync)
def sync_pipedrivesync(instance, sender, created, **kwargs):
instance.sync_with_pipedrive()
I've attempted to modify my Django ModelForm __init__ constructor so that it will accept a passed variable ('admin'), look to see if admin == True, and if so, make a couple of fields ('applicant_affirmation' & 'applicant_interest_stmt') display as non-modifiable. The fields are being shown as non-modifiable, as desired, but the .update() function isn't happening because it is not getting past the if form.is_valid check. I've checked for form.non_field_errors and form.errors, but nothing is there. The updates work fine if the __init__ method is commented out.
Any thoughts on what I might be missing? Admittedly I don't have a strong understanding of building a constructor yet. Help would be very much appreciated.
class ApplicationForm(ModelForm):
class Meta:
model = Application
fields = ('program', 'status', 'applicant_affirmation', 'applicant_interest_stmt', 'applicant_school', 'applicant_major', 'applicant_school_2', 'applicant_major_2', 'gpa_univ', 'estimated_grad_semester')
widgets = {
'applicant_interest_stmt': Textarea(attrs={'class': 'form-control', 'rows': 5}),
'estimated_grad_semester': Select(),
}
def __init__(self, admin, *args, **kwargs):
super(ApplicationForm, self).__init__(*args, **kwargs)
if admin:
self.fields['applicant_interest_stmt'].widget.attrs['disabled'] = 'disabled'
self.fields['applicant_affirmation'].widget.attrs['disabled'] = 'disabled'
def clean(self):
from django.core.exceptions import ValidationError
cleaned_data = super(ApplicationForm, self).clean()
applicant_interest_stmt = cleaned_data.get('applicant_interest_stmt')
applicant_affirmation = cleaned_data.get('applicant_affirmation')
if not applicant_interest_stmt:
msg = u'Please provide an interest statement.'
self._errors["applicant_interest_stmt"] = self.error_class([msg])
if not applicant_affirmation:
msg = u'Please check the affirmation checkbox.'
self._errors["applicant_affirmation"] = self.error_class([msg])
return cleaned_data
Application Model:
class Application(models.Model):
id = models.AutoField(primary_key=True)
program = models.ForeignKey(Program, verbose_name="certificate program")
status = models.ForeignKey(Status, verbose_name="application status")
applicant = models.ForeignKey(Person)
applicant_affirmation = models.BooleanField()
applicant_interest_stmt = models.TextField(verbose_name="In a few sentences, briefly explain why you are interested in this program and what you expect to get out of it")
applicant_school = models.CharField(max_length=100, verbose_name="school (primary)")
applicant_major = models.CharField(max_length=100, verbose_name="major (primary)")
applicant_school_2 = models.CharField(blank=True, max_length=100, verbose_name="school (secondary)")
applicant_major_2 = models.CharField(blank=True, max_length=100, verbose_name="major (secondary)")
gpa_univ = models.DecimalField(max_digits=3, decimal_places=2, verbose_name="GPA (university)")
estimated_grad_semester = models.CharField(max_length=5, verbose_name="estimated graduation semester")
_created = models.DateTimeField(editable=False, blank=False)
_created_by = models.CharField(max_length=150)
_updated = models.DateTimeField(editable=False, blank=False)
_updated_by = models.CharField(max_length=150)
def clean(self):
from django.core.exceptions import ValidationError
cleaned_data = super(Application, self).clean()
if not self.applicant_affirmation:
raise ValidationError('Please check the affirmation checkbox.')
if self.applicant_school_2 == '/':
self.applicant_school_2 = ''
if self.applicant_major_2 == '/':
self.applicant_major_2 = ''
def save(self, *args, **kwargs):
""" On save, update both timestamps """
self._created = datetime.datetime.now()
self._updated = datetime.datetime.now()
return super(Application, self).save(*args, **kwargs)
#end save
def update(self, *args, **kwargs):
""" On update, update only _updated timestamps """
self._updated = datetime.datetime.now()
return super(Application, self).save(*args, **kwargs)
#end update
def __unicode__(self):
return unicode(self.id)
#end unicode
class Meta:
db_table = u'certs_application'
#end meta
Snippet from views.py:
if request.POST:
app_form = ApplicationForm(request.POST)
app_form.fields['estimated_grad_semester'].widget.choices = build_semester_list('', 12)
if app_form.is_valid():
print 'form is valid...'
app_instance = get_object_or_404(Application, id=app_id)
fields = {'program': app_form.cleaned_data['program'],
'status': app_form.cleaned_data['status'],
'applicant_id': application.applicant_id,
'applicant_affirmation': app_form.cleaned_data['applicant_affirmation'],
'applicant_interest_stmt': app_form.cleaned_data['applicant_interest_stmt'],
'applicant_school': app_form.cleaned_data['applicant_school'],
'applicant_major': app_form.cleaned_data['applicant_major'],
'applicant_school_2': app_form.cleaned_data['applicant_school_2'],
'applicant_major_2': app_form.cleaned_data['applicant_major_2'],
'gpa_univ': app_form.cleaned_data['gpa_univ'],
'estimated_grad_semester': app_form.cleaned_data['estimated_grad_semester'],
'_created_by': app_instance._created_by,
'_created': app_instance._created,
'_updated_by': user.eid,
}
try:
application = Application(pk=app_id, **fields)
application.update()
except Exception, exception:
return HttpResponse('Error: ' + str(exception))
return redirect('application_view', app_id=app_id)
else:
print 'app_form is NOT valid'
else:
# -------------------------------------------
# render the application using GET
# -------------------------------------------
app_form = ApplicationForm(admin=admin_user, instance=application)
app_form.fields['estimated_grad_semester'].widget.choices = build_semester_list('', 12)
Final modifications made that resulted in the needed fix:
views.py
if request.POST:
app_form = ApplicationForm(admin=admin_user, data=request.POST)
forms.py
def __init__(self, admin, *args, **kwargs):
super(ApplicationForm, self).__init__(*args, **kwargs)
self.admin = admin
if self.admin:
self.fields['applicant_interest_stmt'].widget.attrs['readonly'] = True
self.fields['applicant_affirmation'].widget.attrs['readonly'] = True
def clean(self):
from django.core.exceptions import ValidationError
cleaned_data = super(ApplicationForm, self).clean()
if not self.admin:
applicant_interest_stmt = cleaned_data.get('applicant_interest_stmt')
applicant_affirmation = cleaned_data.get('applicant_affirmation')
if not applicant_interest_stmt:
msg = u'Please provide an interest statement.'
self._errors["applicant_interest_stmt"] = self.error_class([msg])
if not applicant_affirmation:
msg = u'Please check the affirmation checkbox.'
self._errors["applicant_affirmation"] = self.error_class([msg])
return cleaned_data
NOTE: there is still a lingering problem with getting a non-modifiable setting on the applicant_affirmation Boolean field, but I'll fix that separately from this issue.
you might want to make the admin a golobal to the class
class ApplicationForm(ModelForm):
class Meta:
model = Application
fields = ('program', 'status', 'applicant_affirmation', 'applicant_interest_stmt', 'applicant_school', 'applicant_major', 'applicant_school_2', 'applicant_major_2', 'gpa_univ', 'estimated_grad_semester')
widgets = {
'applicant_interest_stmt': Textarea(attrs={'class': 'form-control', 'rows': 5}),
'estimated_grad_semester': Select(),
}
def __init__(self, admin, *args, **kwargs):
super(ApplicationForm, self).__init__(*args, **kwargs)
self.admin = admin
if self.admin:
self.fields['applicant_interest_stmt'].widget.attrs['disabled'] = 'disabled'
self.fields['applicant_affirmation'].widget.attrs['disabled'] = 'disabled'
def clean(self):
from django.core.exceptions import ValidationError
cleaned_data = super(ApplicationForm, self).clean()
if not self.admin:
applicant_interest_stmt = cleaned_data.get('applicant_interest_stmt')
applicant_affirmation = cleaned_data.get('applicant_affirmation')
if not applicant_interest_stmt:
msg = u'Please provide an interest statement.'
self._errors["applicant_interest_stmt"] = self.error_class([msg])
if not applicant_affirmation:
msg = u'Please check the affirmation checkbox.'
self._errors["applicant_affirmation"] = self.error_class([msg])
return cleaned_data