In my model, I have calculated property current_tobe_payed
I want to generate CSV report of all rows where my property current_tobe_payed is less than zero
See my view below:
def export_leaseterm_csv(request):
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="leaseterm.csv"'
writer = csv.writer(response)
leaseterms = serializers.serialize( "python", LeaseTerm.objects.all())
[obj for obj in leaseterms if obj.current_tobe_payed > 0]
for leaseterm in obj:
writer.writerow(leaseterm['fields'].values())
return response
However, I am getting an error:
'dict' object has no attribute 'current_tobe_payed'
How can I solve this issue?
(also I want to enter only certain fields into CSV and not all the table.)
UPDATE:
See my model below:
class LeaseTerm(CommonInfo):
version = IntegerVersionField( )
start_period = models.ForeignKey(Period, related_name='start_period' )
end_period = models.ForeignKey(Period, related_name='end_period')
lease = models.ForeignKey(Lease)
increase = models.DecimalField(max_digits=7, decimal_places=2)
amount = models.DecimalField(max_digits=7, decimal_places=2)
is_terminated = models.BooleanField(default=False)
# _total = None
_current_period = None
_total_current = None
_total_payment = None
_total_current_payment = None
_total_discount = None
_total_current_discount = None
_current_tobe_payed = None
_current_balance = None
def _get_total(self):
from payment.models import LeasePayment
from conditions.models import LeaseDiscount
total_payment_dict = LeasePayment.objects.filter(leaseterm_id=self.id, is_active = True ).aggregate(Sum('amount'))
if total_payment_dict ['amount__sum']:
total_payment = total_payment_dict['amount__sum']
else:
total_payment = 0
total_discount_dict = LeaseDiscount.objects.filter(leaseterm_id=self.id, is_active = True ).aggregate(Sum('amount'))
if total_discount_dict ['amount__sum']:
total_discount = total_discount_dict['amount__sum']
else:
total_discount = 0
# current = Period.objects.filter( is_active = True, _is_current = True )
current_date=datetime.datetime.now().date()
current_period_dict = Period.objects.filter(start_date__lte=current_date,end_date__gte=current_date, is_active = True ).aggregate(Max('order_value'))
#self._current_period = current_period
if current_period_dict['order_value__max']:
current_period = current_period_dict['order_value__max']
else:
current_period = 0
current_discount_dict = LeaseDiscount.objects.filter(leaseterm_id=self.id,
is_active = True, period_date__gte=self.start_period,
period_date__lte=current_period).aggregate(Sum('amount'))
if current_discount_dict ['amount__sum']:
current_discount = current_discount_dict['amount__sum']
else:
current_discount = 0
current_periods_number = current_period - self.start_period.order_value + 1
current_tobe_payed = current_periods_number * self.amount - current_discount
current_balance = total_payment - current_tobe_payed
self._current_period = current_period
self._total_payment = total_payment
self._total_discount = total_discount
self._current_tobe_payed = current_tobe_payed
self._current_balance = current_balance
#property
def current_tobe_payed(self):
if self._current_tobe_payed is None:
self._get_total()
return self._current_tobe_payed
#property
def current_balance(self):
if self._current_balance is None:
self._get_total()
return self._current_balance
#property
def current_period(self):
if self._current_period is None:
self._get_total()
return self._current_period
#property
def total_payment(self):
if self._total_payment is None:
self._get_total()
return self._total_payment
#property
def total_discount(self):
if self._total_discount is None:
self._get_total()
return self._total_discount
def clean(self):
model = self.__class__
if self.lease_id and (self.is_terminated == False) and (self.is_active == True) and model.objects.filter(lease=self.lease, is_active=True ).exclude(id=self.id).count() == 1:
raise ValidationError('!Lease has a active condition already, Terminate prior to creation of new one'.format(self.lease))
def save(self, *args, **kwargs):
self.full_clean()
return super(LeaseTerm, self).save(*args, **kwargs)
def __unicode__(self):
return u'%s %i %s %s ' % ("term:",self.id, self.start_period, self.end_period)
That's rather lengthy calculation that you have in your get_total method. I count five queries inside that the following bit of code will result in those five queries being executed for each row on your table.
[obj for obj in leaseterms if obj.current_tobe_payed > 0]
So that means you are doing 5000 queries if you have just a 1000 rows in your table. With 10,000 rows, this list comprehension would take a very long time to run.
Solution. Convert your property to a model field.
to_be_payed = models.DecimalField(max_digits=7, decimal_places=2)
I am often telling deves not to save the results of simple calculations into a db column. but yours isn't a simple calculation but a complex one so it deserves a field. YOu can update this field in the save method
def save(self, *args, **kwargs):
self.to_be_payed = self.get_total()
super(LeaseTerm, self).save(*args, **kwargs)
If as you say, the amount to be paid depends on changes to a Payment instance, what you can do is to have a post_save signal on the Payment model to trigger the related LeaseTerm object(s) to be updated. Doing such an update would still be cheaper than doing this calculation 5000 times
You are using a serializer which returns a python dictionary object. It is not an instance of a model. I suggest the following:
EDITED SOLUTION
def export_leaseterm_csv(request):
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="leaseterm.csv"'
writer = csv.writer(response)
# get all the LeaseTerm instances
leaseterms = LeaseTerm.objects.all()
# filter based on current_tobe_payed
tobe_payed_terms = [obj for obj in leaseterms if obj.current_tobe_payed > 0]
tobe_payed_dict = serializers.serialize( "python", tobe_payed_terms)
# serialize these objects and write to values to the csv
for term in tobe_payed_dict:
writer.writerow(term['fields'].values())
return response
at the end I did it without signal and without sterilizer
Amount of records in this table will never grow more then 100th .This report is executed only by one person in company once a week. During the testing if the performance will be insufficient I will denormalize other then that I prefer to have it normalized as long as I can.
def export_leaseterm_csv(request):
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="leaseterm.csv"'
writer = csv.writer(response)
writer.writerow([
"lease",
"tenant",
"amount",
"current balance",
])
leaseterms = LeaseTerm.objects.filter(is_terminated = False, is_active = True )
tobe_payed_terms = [obj for obj in leaseterms if obj.current_balance < 0]
for term in tobe_payed_terms:
writer.writerow([
term.lease,
term.tenant,
term.amount,
term.current_balance,
])
return response
Related
I'm trying to test if my PlayerPoint model can give me the top 5 players in regards to their points.
This is the Player model:
class Player(AbstractUser):
phone_number = models.CharField(
max_length=14,
unique=True,
help_text="Please ensure +251 is included"
)
and this is the PlayerPoint model:
class PlayerPoint(models.Model):
OPERATION_CHOICES = (('ADD', 'ADD'), ('SUB', 'SUBTRACT'), ('RMN', 'REMAIN'))
points = models.IntegerField(null=False, default=0)
operation = models.CharField(
max_length=3,
null=False,
choices=OPERATION_CHOICES,
default=OPERATION_CHOICES[2][0]
)
operation_amount = models.IntegerField(null=False)
operation_reason = models.CharField(null=False, max_length=1500)
player = models.ForeignKey(
settings.AUTH_USER_MODEL,
null=False,
on_delete=models.PROTECT,
to_field="phone_number",
related_name="player_points"
)
points_ts = models.DateTimeField(auto_now_add=True, null=False)
class Meta:
get_latest_by = ['pk', 'points_ts']
I also have a pre-save signal handler:
#receiver(signals.pre_save, sender=PlayerPoint)
def pre_save_PlayerPoint(sender, instance, **_):
if sender is PlayerPoint:
try:
current_point = PlayerPoint.objects.filter(player=instance.player).latest()
except PlayerPoint.DoesNotExist as pdne:
if "new player" in instance.operation_reason.lower():
print(f"{pdne} {instance.player} must be a new")
instance.operation_amount = 100
instance.points = int(instance.points) + int(instance.operation_amount)
else:
raise pdne
except Exception as e:
print(f"{e} while trying to get current_point of the player, stopping execution")
raise e
else:
if instance.operation == PlayerPoint.OPERATION_CHOICES[0][0]:
instance.points = int(current_point.points) + int(instance.operation_amount)
elif instance.operation == PlayerPoint.OPERATION_CHOICES[1][0]:
if int(current_point.points) < int(instance.operation_amount):
raise ValidationError(
message="not enough points",
params={"points": current_point.points},
code="invalid"
)
instance.points = int(current_point.points) - int(instance.operation_amount)
As you can see there is a foreign key relation.
Before running the tests, in the setUp() I create points for all the players as such:
class Top5PlayersViewTestCase(TestCase):
def setUp(self) -> None:
self.player_model = get_user_model()
self.test_client = Client(raise_request_exception=True)
self.player_list = list()
for i in range(0, 10):
x = self.player_model.objects.create_user(
phone_number=f"+2517{i}{i}{i}{i}{i}{i}{i}{i}",
# first_name="test",
# father_name="user",
# grandfather_name="tokko",
# email=f"test_user#tokko7{i}.com",
# age="29",
password="password"
)
PlayerPoint.objects.create(
operation="ADD",
operation_reason="new player",
player=x
)
self.player_list.append(x)
counter = 500
for player in self.player_list:
counter += int(player.phone_number[-1:])
PlayerPoint.objects.create(
operation="ADD",
operation_amount=counter,
operation_reason="add for testing",
player=player
)
PlayerPoint.objects.create(
operation="ADD",
operation_amount=counter,
operation_reason="add for testing",
player=player
)
return super().setUp()
def test_monthly_awarding_view_displays_top5_players(self):
for player in self.player_list:
print(player.player_points.latest())
# self.test_client.post("/accounts/login/", self.test_login_success_data)
test_results = self.test_client.get("/points_app/monthly_award/", follow=True)
self.assertEqual(test_results.status_code, 200)
self.assertTemplateUsed(test_results, "points_app/monthlytop5players.html")
self.assertEqual(len(test_results.context.get('results')), 5)
top_5 = PlayerPoint.objects.order_by('-points')[:5]
for pt in top_5:
self.assertIn(pt, test_results.context.get('results'))
The full traceback is this after running coverage run manage.py test points_app.tests.test_views.MonthlyAwardingViewTestCase.test_monthly_awarding_view_displays_top5_players -v 2:
Traceback (most recent call last):
File "/home/gadd/vscodeworkspace/websites/25X/twenty_five_X/points_app/tests/test_views.py", line 358, in test_monthly_awarding_view_displays_top5_players
self.assertIn(pt, test_results.context.get('results'))
AssertionError: <PlayerPoint: 1190 -- +251799999999> not found in [<User25X: +251700000000>, <User25X: +251711111111>, <User25X: +251722222222>, <User25X: +251733333333>, <User25X: +251744444444>]
This is the view being tested:
def get(request):
all_players = get_user_model().objects.filter(is_staff=False).prefetch_related('player_points')
top_5 = list()
for player in all_players:
try:
latest_points = player.player_points.latest()
except Exception as e:
print(f"{player} -- {e}")
messages.error(request, f"{player} {e}")
else:
if all(
[
latest_points.points >= 500,
latest_points.points_ts.year == current_year,
latest_points.points_ts.month == current_month
]
):
top_5.append(player)
return render(request, "points_app/monthlytop5players.html", {"results": top_5[:5]})
What am I doing wrong?
There are 2 problems.
In your view, top_5 is an unsorted list of Player objects.
top_5 = sorted(top_5, key=lambda player: player.player_points.latest().points, reverse=True)[:5] # Add this
return render(request, "points_app/monthlytop5players.html", {"results": top_5[:5]})
In your test, top_5 is a list (actually QuerySet) of PlayerPoint objects.
results_pts = [player.player_points.latest() for player in test_results.context['results']] # Add this
for pt in top_5:
# self.assertIn(pt, test_results.context.get('results')) # Change this
self.assertIn(pt, results_pts) # to this
I think your problem is with this line:
latest_points = player.player_points.latest()
Specifically, latest(). Like get(), earliest() and latest() raise DoesNotExist if there is no object with the given parameters.
You may need to add get_latest_by to your model's Meta class. Maybe try this:
class PlayerPoint(models.Model):
...
class Meta:
get_latest_by = ['joined_ts']
If you don't want to add this to your model, you could just do it directly:
latest_points = player.player_points.latest('-joined_ts')
if this is the problem.
As far as I can tell the assert calls for id and goal_id AND my code provides them both...
enter image description here
goal_routes.py
from datetime import datetime from typing import OrderedDict from
urllib.request import OpenerDirector from flask import Blueprint,
jsonify, request, make_response, abort from app import db from
app.models.goal import Goal from app.models.task import Task from
app.task_routes import validate_task
Create a Goal: goal_bp = Blueprint("goal_bp", name, url_prefix="/goals")
#goal_bp.route("", methods = ["POST"]) def create_goals():
request_body = request.get_json()
if "title" in request_body:
new_goal = Goal(
title = request_body["title"]
)
else:
return jsonify({"details":"Invalid data"}), 400
db.session.add(new_goal)
db.session.commit()
goal_response = {"goal": new_goal.to_dictionary()}
return (jsonify(goal_response), 201)
Get Goals #goal_bp.route("", methods = ["GET"]) def get_goals():
sort = request.args.get("sort")
#Sort by assending (is default?)
if sort == "asc":
goals =Goal.query.order_by(Goal.title)
#Sort by decending
elif sort == "desc":
goals =Goal.query.order_by(Goal.title.desc())
#No Sort
else:
goals = Goal.query.all()
goals_response = []
for goal in goals:
goals_response.append(goal.to_dictionary())
# If No Saved Goals wil stil return 200
return (jsonify(goals_response), 200)
Get One Goal: One Saved Goal #goal_bp.route("/<goal_id>", methods=["GET"]) def get_one_goal(goal_id):
goal = validate_goal(goal_id)
goal_response = {"goal": goal.to_dictionary()}
return (jsonify(goal_response), 200)
Update Goal #goal_bp.route("/<goal_id>", methods=["PUT"]) def update_goal(goal_id):
goal = validate_goal(goal_id)
request_body = request.get_json()
goal.title = request_body["title"]
db.session.commit()
goal_response = {"goal": goal.to_dictionary()}
return (jsonify(goal_response), 200)
Goal Complete #goal_bp.route("/<goal_id>/mark_complete", methods=["PATCH"]) def goal_complete(goal_id):
goal = validate_goal(goal_id)
goal.completed_at = datetime.utcnow()
db.session.commit()
goal_response = {"goal": goal.to_dictionary()}
return (jsonify(goal_response), 200)
Goal Incomplete #goal_bp.route("/<goal_id>/mark_incomplete", methods=["PATCH"]) def goal_incomplete(goal_id):
goal = validate_goal(goal_id)
goal.completed_at = None
db.session.commit()
goal_response = {"goal": goal.to_dictionary()}
return (jsonify(goal_response), 200)
Delete Goal: Deleting a Goal #goal_bp.route("/<goal_id>", methods=["DELETE"]) def delete_goal(goal_id):
goal = validate_goal(goal_id)
db.session.delete(goal)
db.session.commit()
response = {"details": f"Goal {goal.goal_id} \"{goal.title}\" successfully deleted"}
return (jsonify(response), 200)
Validate there are no matching Goal: Get, Update, and Delete
def validate_goal(goal_id):
try:
goal_id = int(goal_id)
except:
abort(make_response({"message": f"Goal {goal_id} is invalid"}, 400))
goal = Goal.query.get(goal_id)
if not goal:
abort(make_response({"message": f"Goal {goal_id} not found"}, 404))
return goal
#goal_bp.route("/<goal_id>/tasks", methods=["POST"]) def
post_task_ids_to_goal(goal_id):
goal = validate_goal(goal_id)
request_body = request.get_json()
for task_id in request_body["task_ids"]:
task = Task.query.get(task_id)
task.goal_id = goal_id
task.goal = goal
db.session.commit()
return jsonify({"id":goal.goal_id, "task_ids": request_body["task_ids"]}), 200
#goal_bp.route("/<goal_id>/tasks", methods=["GET"]) def
get_tasks_for_goal(goal_id):
goal = validate_goal(goal_id)
task_list = [task.to_dictionary() for task in goal.tasks]
goal_dict = goal.to_dictionary()
goal_dict["tasks"] = task_list
return jsonify(goal_dict)
goal.py
from app import db
class Goal(db.Model):
goal_id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String, nullable=False)
tasks = db.relationship("Task", back_populates="goals", lazy = True)
def to_dictionary(self):
goal_dict = {
"id": self.goal_id,
"title": self.title
}
if self.tasks:
goal_dict["tasks"] = [task.task_id for task in self.tasks]
return goal_dict
My DetailView currently gets longer and longer. And therefore I am trying to learn more about some better practices to shorten it again. Right now, I am looking into the def post() part. The full code is even longer. My best idea currently is to create a utils.py in the same folder and take out the code snippets inside the if else statements. Is that best practice or would you do it in a different way?
class EventDetail(DetailView):
[...]
#transaction.atomic
def post(self, request, *args, **kwargs):
# Discount code POST
if self.discount_form.is_valid():
discount_code = self.discount_form.cleaned_data['code']
request.session[discount_cookie_name(request.event)] = discount_code
return redirect(reverse_obj(self.request.event, 'events:detail'))
# Consent form POST
elif self.consent_form.is_valid():
# Check if email entry exists
contact = request.event.organizer.contacts.filter(
email=self.consent_form.cleaned_data['email']
).first()
success_message = _("You successfully signed up to receive event updates.")
if contact:
# Check if current event is allocated to entered email.
event_entry = contact.contact_relation.filter(
event=request.event
).first()
if event_entry:
event_entry.lead = True
event_entry.save(update_fields=['lead'])
messages.success(request, success_message)
else:
# Email entry exists, but event allocation must be added.
ContactRelation.objects.create(
lead=True, contact=contact, event=request.event
)
messages.success(request, success_message)
# Create new email entry and allocate event.
else:
instance = self.consent_form.save(commit=False)
instance.organizer = request.event.organizer
instance.save()
instance.contact_relations.add(
request.event, through_defaults={'lead': True}
)
messages.success(request, success_message)
return redirect(reverse_obj(self.request.event, 'events:detail'))
elif self.formset.is_valid():
# Check if the user already has an order_reference in the session
old_order_reference = request.session.get('order_reference')
if old_order_reference is not None:
ReservedItem.objects.filter(
order_reference=old_order_reference
).delete()
# Generate a new order_reference and save it in the session
order_reference = unique_order_reference_generator()
request.session['order_reference'] = order_reference
for form in self.formset:
quantity = form.cleaned_data['quantity']
qty = 0 if not quantity else quantity
if qty > 0:
reserved_item = form.save(commit=False)
ticket = form.cleaned_data['ticket']
reserved_item.order_reference = order_reference
reserved_item.ticket_name = ticket.name
if ticket.tax:
reserved_item.ticket_tax = ticket.tax
reserved_item.tax_name = ticket.tax.name
reserved_item.tax_percentage = ticket.tax.percentage
# Prevent AttributeError
discount_obj = None
if self.discount_code:
discount_obj = self.discount_code
if self.social_ticketing_referral:
# That part is not inside generate_reserved_discount_items
# as the influencer is still getting points. Even, if the ticket
# has no discount.
reserved_item.social_ticketing_referral = (
self.social_ticketing_referral.ambassador
)
# Add points earned per ticket
reserved_item.social_ticketing_unit_points_earned = (
ticket.points.points
if self.social_ticketing.is_point_mode_manual()
else monetary_amount_to_points(
ticket.price_gross, request.event.currency
)
)
# Check if Social Ticketing has incentive discount (not talking point).
# That also means self.discount_code doesn't apply
if self.social_ticketing.is_incentive_discount():
discount_obj = self.social_ticketing.discount
reserved_item.discount_code = (
self.social_ticketing_referral.code
)
if discount_obj:
generate_reserved_discount_items(
reserved_item=reserved_item,
discount=discount_obj,
ticket=ticket,
quantity=quantity,
)
# Save reserved item
reserved_item.save()
return redirect('checkout:checkout', step='start')
return super().get(request, *args, **kwargs)
Working with django-rest-framework I'm using a serializer with many=True, checking for items which already exist and invalidating them.
The problem is:
When part of a request is invalid, the whole request is rejected without creating the valid objects.
Sample Payload:
[{'record_timestamp': '2016-03-04T09:46:04', 'reader_serial': u'00000000f9b320ac', 'card_serial': u'048EC71A0F3382', 'gps_latitude': None, 'gps_longitude': None, 'salt': 34, 'reader_record_id': 1063},
{'record_timestamp': '2016-03-04T09:46:06', 'reader_serial': u'00000000f9b320ac', 'card_serial': u'04614B1A0F3382', 'gps_latitude': None, 'gps_longitude': None, 'salt': 34, 'reader_record_id': 1064}]
Sample response:
[{"last_record_id":[2384],"error":["This record already exists"]},{}]
Ideal response:
[{"last_record_id":[2384],"error":["This record already exists"]},{'reader': 10, 'card': 12, 'gps_latitude': None, 'gps_longitude': None, 'reader_record_id': 1064}}]
I'd like the first record to provide the error, but the second record to be correctly created, with the response being the object created.
class CardRecordInputSerializer(serializers.ModelSerializer):
class Meta:
model = CardRecord
fields = ('card', 'reader', 'bus', 'park', 'company', 'client',
'record_timestamp', 'reader_record_id')
read_only_fields = ('card', 'reader', 'bus', 'park', 'company'
'client')
def validate(self, data):
"""
Check that the record is unique
"""
#import ipdb; ipdb.set_trace()
hash_value = data.get("hash_value", None)
if CardRecord.objects.filter(hash_value=hash_value):
raise ValidationError(
detail={"error":"This record already exists",
"last_record_id":data.get("reader_record_id", None)})
else:
return data
def to_internal_value(self, data):
internal_value = super(CardRecordInputSerializer, self)\
.to_internal_value(data)
card_serial = data.get("card_serial", None).upper()
reader_serial = data.get('reader_serial', None).upper()
record_timestamp = data.get('record_timestamp', None)
date_altered = False
record_date = dateutil.parser.parse(record_timestamp)
#check if clock has reset to 1970
if record_date < datetime.datetime(2014, 4, 24):
record_date = datetime.datetime.now().isoformat()
date_altered = True
#create a hash to check that this record is unique
salt = data.get('salt', None)
hash_generator = hashlib.sha1()
hash_generator.update(card_serial)
hash_generator.update(reader_serial)
hash_generator.update(str(record_timestamp))
hash_generator.update(str(salt))
hash_value = str(hash_generator.hexdigest())
internal_value.update({
"card_serial": card_serial,
"reader_serial": reader_serial,
"salt": salt,
"hash_value": hash_value,
"record_timestamp": record_date,
"date_altered": date_altered
})
return internal_value
def create(self, validated_data):
#import ipdb; ipdb.set_trace()
'''
Create a new card transaction record
'''
try:
card_serial = validated_data.get('card_serial', None)
card = Card.objects.filter(uid=card_serial).last()
reader_serial = validated_data.get('reader_serial', None)
reader = Reader.objects.filter(serial=reader_serial).last()
#if we havent seen this reader before, add it to the list
if not reader:
reader = Reader.objects.create(serial=reader_serial)
company = card.company
client = reader.client
park = reader.park
record_timestamp = validated_data.get('record_timestamp', None)
reader_record_id = validated_data.get('reader_record_id', None)
#if datetime is naive, set it to utc
if record_timestamp.tzinfo is None \
or record_timestamp.tzinfo.utcoffset(d) is None:
record_timestamp = pytz.utc.localize(record_timestamp)
hash_value = validated_data.get('hash_value', None)
date_altered = validated_data.get('date_altered', None)
return CardRecord.objects.create(card = card,
reader = reader,
company = company,
client = client,
park = park,
record_timestamp = record_timestamp,
reader_record_id = reader_record_id,
hash_value = hash_value,
date_altered = date_altered)
#Usually a card that doesn't have company
except AttributeError:
return {
'status': 'Bad Request',
'message': 'One of the values was malformed or does not exist.'
}
How can I create valid objects and provide errors for the invalid ones?
I ended up skipping the validation.
Then in my create method if the object already exists I just return it, if it doesn't exist I create it and return it.
The client no longer knows it the server had that record, but thats fine for my use case.
I also swapped to using PUT to reflect the fact that the method is idempotent.
I feel like the validator is the place to do the check but this works.
class CardRecordInputSerializer(serializers.ModelSerializer):
class Meta:
model = CardRecord
fields = ('card', 'reader', 'bus', 'park', 'company', 'client',
'record_timestamp', 'reader_record_id')
read_only_fields = ('card', 'reader', 'bus', 'park', 'company'
'client')
def validate(self, data):
"""
Check that the record is unique
"""
#import ipdb; ipdb.set_trace()
#<--------Removed the validation
return data
def to_internal_value(self, data):
internal_value = super(CardRecordInputSerializer, self)\
.to_internal_value(data)
card_serial = data.get("card_serial", None).upper()
reader_serial = data.get('reader_serial', None).upper()
record_timestamp = data.get('record_timestamp', None)
date_altered = False
record_date = dateutil.parser.parse(record_timestamp)
#check if clock has reset to 1970
if record_date < datetime.datetime(2014, 4, 24):
record_date = datetime.datetime.now().isoformat()
date_altered = True
#create a hash to check that this record is unique
salt = data.get('salt', None)
hash_generator = hashlib.sha1()
hash_generator.update(card_serial)
hash_generator.update(reader_serial)
hash_generator.update(str(record_timestamp))
hash_generator.update(str(salt))
hash_value = str(hash_generator.hexdigest())
internal_value.update({
"card_serial": card_serial,
"reader_serial": reader_serial,
"salt": salt,
"hash_value": hash_value,
"record_timestamp": record_date,
"date_altered": date_altered
})
return internal_value
def create(self, validated_data):
#import ipdb; ipdb.set_trace()
'''
Create a new card transaction record
'''
try:
card_serial = validated_data.get('card_serial', None)
card = Card.objects.filter(uid=card_serial).last()
reader_serial = validated_data.get('reader_serial', None)
reader = Reader.objects.filter(serial=reader_serial).last()
#if we havent seen this reader before, add it to the list
if not reader:
reader = Reader.objects.create(serial=reader_serial)
company = card.company
client = reader.client
park = reader.park
record_timestamp = validated_data.get('record_timestamp', None)
reader_record_id = validated_data.get('reader_record_id', None)
#if datetime is naive, set it to utc
if record_timestamp.tzinfo is None \
or record_timestamp.tzinfo.utcoffset(d) is None:
record_timestamp = pytz.utc.localize(record_timestamp)
hash_value = validated_data.get('hash_value', None)
date_altered = validated_data.get('date_altered', None)
record = CardRecord.objects.filter(hash_value=hash_value).last()
if record: #<--------Check if that object already exists
return record #<-------- if it does just return it
else: #<-------- otherwise make it
return CardRecord.objects.create(
card = card,
reader = reader,
company = company,
client = client,
park = park,
record_timestamp = record_timestamp,
reader_record_id = reader_record_id,
hash_value = hash_value,
date_altered = date_altered)
#Usually a card that doesn't have company
except AttributeError:
return {
'status': 'Bad Request',
'message': 'One of the values was malformed or does not exist.'
}
I started this thread and managed to figure out the issue yesterday.
However, another related issue has occurred.
When I have a 2nd select list in the model, and I select '8888' or '9999' to remove / reset the values when the form is submitted, how can I set the value of the 2nd select list to the default value?
Here is an example of the models.py code I have:
.....
DISPLAY_ONLY_AWARD_AND_GRANT_DESCRIPTION_WITH_PROMPT = 8888
DISPLAY_ONLY_AWARD_AND_GRANT_DESCRIPTION_WITHOUT_PROMPT = 9999
.....
AWARD_GRANT_TYPES = (
(SELECT_AWARD_AND_GRANT_TYPE, _('Select Type')),
....
(OTHER_GRANT, _('Other Grant')),
(WRITE_MY_OWN_AWARD_AND_GRANT_TYPE_DESCRIPTION, _('Write my own Type description')), #7777
(DISPLAY_ONLY_AWARD_AND_GRANT_DESCRIPTION_WITH_PROMPT, _('Display only Description with prompt')), #8888
(DISPLAY_ONLY_AWARD_AND_GRANT_DESCRIPTION_WITHOUT_PROMPT, _('Display only Description without prompt')) #9999
)
....
AWARD_GRANT_DISPLAY_COMMENCEMENT_DATE_AND_COMPLETION_DATE_AS_DATE_PROMPTS = 1
....
AWARD_GRANT_DETAILS_DATE_DISPLAY_TYPES = (
(AWARD_GRANT_DISPLAY_COMMENCEMENT_DATE_AND_COMPLETION_DATE_AS_DATE_PROMPTS, _('Display Commencement and Completion as date prompts')),
....
)
user = models.ForeignKey(User)
language_version = models.ForeignKey('LanguageVersion')
award_grant_type = models.PositiveIntegerField(choices=AWARD_GRANT_TYPES, default=SELECT_AWARD_AND_GRANT_TYPE, validators=[MinValueValidator(1)])
award_grant_type_description = models.CharField(null=True, blank=True, max_length=250)
award_grant_date = models.DateField(null=True, blank=True)
award_grant_date_display_type = models.PositiveIntegerField(choices=AWARD_GRANT_DETAILS_DATE_DISPLAY_TYPES, default=AWARD_GRANT_DISPLAY_COMMENCEMENT_DATE_AND_COMPLETION_DATE_AS_DATE_PROMPTS)
award_grant_description = models.TextField(null=False, blank=False, max_length=5000)
Here is my forms.py clean code that should reset the award_grant_date_display_type to 1 when the user has selected 8888 or 9999 from the select list award_grant_type before being committed to the db:
def clean(self):
cd_agdf = super(AwardGrantDetailsForm, self).clean()
if 'award_grant_type' in cd_agdf:
if cd_agdf['award_grant_type'] == '':
self._errors['award_grant_type'] = self.error_class([_("You must select a Type.")])
elif cd_agdf['award_grant_type'] == '8888' or cd_agdf['award_grant_type'] == '9999':
# remove / reset the entered values when the award grant type only requires minimum data.
self.cleaned_data.pop('award_grant_type_description', None)
self.cleaned_data.pop('award_grant_date', None)
self.cleaned_data['award_grant_date_display_type'] = 1
else:
....
return cd_agdf
It appears that the line of code self.cleaned_data['award_grant_date_display_type'] = 1 is ignored and the original value retained.
I have tried placing the value in quotation marks, searched SO, Google and django docs, but I cannot figure this out.
EDIT
Here is my forms.py code as requested:
class AwardGrantDetailsForm(forms.ModelForm):
def __init__(self, available_languages, language_preference, *args, **kwargs):
"""
available_languages should be a valid choices list
"""
super(AwardGrantDetailsForm, self).__init__(*args, **kwargs)
self.fields['language_code'] = forms.ChoiceField(choices=available_languages, initial=language_preference, label=_('Language'),)
self.fields['award_grant_date'].input_formats = (settings.DATE_INPUT_FORMATS)
class Meta:
model = AwardGrantDetails
fields = (
'award_grant_type',
'award_grant_type_description',
'award_grant_date',
'award_grant_date_display_type',
'award_grant_description',
)
labels = {
'award_grant_type': _('Type'),
'award_grant_type_description': _('Type Description'),
'award_grant_date': _('Date'),
'award_grant_date_display_type': _('Date Prompts'),
'award_grant_description': _('Description'),
}
help_texts = {
'award_grant_description': _('5,000 character limit'),
}
input_formats = {
'award_grant_date': settings.DATE_INPUT_FORMATS,
}
widgets = {
#'award_grant_description': CKEditorWidget(),
'award_grant_date': forms.DateInput(attrs={'id': 'id_award_grant_date'}, format='%m/%Y'),
#'award_grant_date': forms.DateInput(attrs={'id': 'id_award_grant_date'}, format=settings.DATE_INPUT_FORMATS),
}
error_messages = {
'award_grant_type': {'validate_min': _('This field is required.')}, # validate_min used on award_grant_type in lieu of required field.
}
EDIT
Here is my views.py code as requested:
#login_required
#resume_menu_required(entry_number=settings.MENU_DETAIL_VALUE_AWARD_GRANT_DETAILS)
#complete_profile_required
def award_grant_details_edit(request, award_grant_details_id):
try:
award_grant_details = AwardGrantDetails.objects.get(pk=award_grant_details_id, user=request.user)
except AwardGrantDetails.DoesNotExist:
return redirect(settings.MENU_DETAIL_LINK_AWARD_GRANT_DETAILS)
language_versions = LanguageVersion.objects.filter(user=request.user).select_related('language_version')
available_languages = get_available_language_details(language_versions, request.user.userprofile.language_preference)
award_grant_details_num = request.user.awardgrantdetails_set.count()
language_code = award_grant_details.language_version.language_code
language_code_disabled = award_grant_details.language_version.language_code_disabled
preview_labels = get_award_grant_types(available_languages)
selected_resume_menu_entries = (request.user.userprofile.selected_resume_menu_entries)
if language_code_disabled:
return redirect(settings.MENU_DETAIL_LINK_AWARD_GRANT_DETAILS)
if request.method == 'GET':
language_code = award_grant_details.language_version.language_code
form = AwardGrantDetailsForm(
available_languages=available_languages,
language_preference=request.user.userprofile.language_preference,
initial=dict(model_to_dict(award_grant_details), language_code=language_code))
elif request.method == 'POST':
form = AwardGrantDetailsForm(
available_languages=available_languages,
language_preference=request.user.userprofile.language_preference,
data=request.POST)
if form.is_valid():
award_grant_details.fill(form.cleaned_data)
award_grant_details.save()
messages.success(request, _('successfully updated.'))
return redirect(settings.MENU_DETAIL_LINK_AWARD_GRANT_DETAILS)
return render(request,'resume_details/award_grant_details_edit.html',{
'address_details_count': get_address_details_count(request.user),
'award_grant_details': award_grant_details,
'award_grant_details_num': award_grant_details_num,
'contact_details_count': get_contact_details_count(request.user),
'display_default_language': display_default_language(request.user),
'form': form,
'language_versions': get_language_versions(user=request.user),
'language_versions_num': len(language_versions), # the count of all users Language Versions (enabled & disabled).
'languages': LANGUAGES,
'max_award_grant_details': settings.MAX_AWARD_GRANT_DETAILS,
'max_details_count': settings.MAX_AWARD_GRANT_DETAILS,
'name_details_count': get_name_details_count(request.user),
'preview_labels': preview_labels,
'resume_details_menu_link': settings.MENU_DETAIL_LINK_AWARD_GRANT_DETAILS,
'resume_details_menu_num': settings.MENU_DETAIL_VALUE_AWARD_GRANT_DETAILS,
'selected_resume_menu_entries': selected_resume_menu_entries,
})
I never discovered the answer to this issue.
However, a workaround is that on form submit when 8888 or 9999 is selected and there are no errors, I manually set the value to 1 using jquery:
$('#id_of_element_type').val('1');
I hope that this will assist someone.