I am building an API for a game and need to select a random game round for a randomly chosen resource.
Choosing the random resource works. What I am trying to do now, in order to coordinate players is to filter the game rounds by the resource that has been chosen randomly and then return a random game round.
The code below shows what I have tried so far, namely to access the resource for which a game round has been played over the method with the #property decorator.
models.py
class Resource(models.Model):
id = models.PositiveIntegerField(null=False, primary_key=True)
hash_id = models.CharField(max_length=256)
creators = models.ManyToManyField(Creator)
titles = models.ManyToManyField(Title)
created_start = models.DateField(null=True)
created_end = models.DateField(null=True)
location = models.CharField(max_length=512, null=True)
institution_source = models.CharField(max_length=512, blank=True)
institution = models.CharField(max_length=512, blank=True)
origin = models.URLField(max_length=256, null=True)
enabled = models.BooleanField(default=True)
media_type = models.CharField(max_length=256, default='picture')
objects = models.Manager()
def __str__(self):
return self.hash_id or ''
#property
def tags(self):
tags = self.taggings.values('tag').annotate(count=Count('tag'))
return tags.values('tag_id', 'tag__name', 'tag__language', 'count')
class Gameround(models.Model):
user = models.ForeignKey(CustomUser, on_delete=models.SET_NULL, null=True)
gamesession = models.ForeignKey(Gamesession, on_delete=models.CASCADE)
created = models.DateTimeField(editable=False)
score = models.PositiveIntegerField(default=0)
objects = models.Manager()
def save(self, *args, **kwargs):
if not self.id:
self.created = timezone.now()
return super().save(*args, **kwargs)
def create(self):
pass
#property
def tags(self):
tags = self.taggings.values('tag')
return tags.values('tag_id', 'tag__name', 'tag__language', 'resource_id')
class Tagging(models.Model):
user = models.ForeignKey(CustomUser, on_delete=models.SET_NULL, null=True)
gameround = models.ForeignKey(Gameround, on_delete=models.CASCADE, related_name='taggings')
resource = models.ForeignKey(Resource, on_delete=models.CASCADE, related_name='taggings')
tag = models.ForeignKey(Tag, on_delete=models.CASCADE)
created = models.DateTimeField(editable=False)
score = models.PositiveIntegerField(default=0)
origin = models.URLField(max_length=256, blank=True, default='')
objects = models.Manager()
def create(self, tag):
tagging = self.create(tag=tag)
def __str__(self):
return str(self.tag) or ''
def save(self, *args, **kwargs):
if not self.id:
self.created = timezone.now()
return super().save(*args, **kwargs)
I am then serialising the game round like this:
*serializers.py
class GameroundSerializer(serializers.ModelSerializer):
gamesession = GamesessionSerializer(read_only=True)
user = CustomUserSerializer(read_only=True)
tags_to_compare = serializers.SerializerMethodField('get_tags_to_compare')
class Meta:
model = Gameround
fields = ['id', 'user', 'gamesession', 'created', 'score', 'tags_to_compare']
def get_tags_to_compare(self, round):
taggings = round.tags
return taggings
def to_representation(self, data):
data = super().to_representation(data)
return data
In the view I have a get request which first chooses a random resource, then I am trying to filter the game rounds to only include the ones where this resource was involved and then I am trying to retrieve a random game round.
views.py
class GameroundView(APIView):
"""
API endpoint for game rounds
"""
def get(self, request, *args, **kwargs):
controller = GameViewController()
random_resource = controller.get_resource()
gameround = Gameround.objects.all().filter(taggings__resource_id=random_resource).order_by("?").first()
gameround_serializer = GameroundSerializer(gameround)
return Response(gameround_serializer.data)
This does not seem to be working since I keep getting the error "The response content must be rendered before it can be iterated over."
I have also tried creating an id variable and passing it on as the resource_id like this:
# id of the random Resource for the game round
random_resource_id = random_resource.get(random_resource.id)
gameround = Gameround.objects.all().filter(taggings__resource_id=random_resource_id).order_by("?").first()
This leads to the 'Response' object has no attribute 'id' error when I reload the page in the browser.
Does anyone know how I can solve this issue?
Thank you in advance!
Below also the Gameviewcontroller that is inside the views.py file
class GameViewController:
def get_random_object(self, MyModel):
random_object = None
object_count = MyModel.objects.all().count() + 1
while not MyModel.objects.all().filter(id=random_object).exists():
for obj in range(object_count):
n = random.randint(1, object_count)
if MyModel.objects.all().filter(id=n).exists():
random_object = n
return random_object
def get_random_id(self, MyModel):
"""Method for large querysets"""
random_object = None
object_count = MyModel.objects.all().count() + 1
for obj in range(object_count):
n = random.randint(1, object_count)
while not MyModel.objects.all().filter(id=n).exists():
alt = random.randint(1, object_count)
if MyModel.objects.all().filter(id=alt).exists():
random_object = n
return random_object
def get_resource(self):
random_resource_id = self.get_random_object(Resource)
current_resource = Resource.objects.all().filter(id=random_resource_id)
resource_serializer = ResourceSerializer(current_resource, many=True)
data = {'resource': resource_serializer.data}
return Response(data, status=status.HTTP_200_OK)
def get_gameround_matching_resource(self, random_resource):
"""Checks if a previously played game round exists for a randomly chosen resource"""
current_gameround = Gameround.objects.all().filter(ressource__in=random_resource).order_by('?').first()
if current_gameround.exists():
gameround_serializer = GameroundSerializer(current_gameround)
data = {'gameround': gameround_serializer.data}
return Response(data, status=status.HTTP_200_OK)
def timer(self, start):
"""Start a new timer as soon as a gameround has been selected"""
start_time = start
elapsed_time = None
if start_time is not None:
start_time = time.perf_counter()
"""Stop the timer, and return the elapsed time"""
if start_time is None:
elapsed_time = time.perf_counter() - start_time
return elapsed_time
def get_gamesession(self):
"""Retrieves a randomly chosen gamesession object"""
current_gamesession = Gamesession.objects.all().order_by('?').first()
gamesession_serializer = GamesessionSerializer(current_gamesession)
data = {'gameround': gamesession_serializer.data}
return Response(data, status=status.HTTP_200_OK)
I finally managed to solve it by rewriting the get request like this:
def get(self, request, *args, **kwargs):
random_resource = Resource.objects.all().order_by('?').first()
resource_serializer = ResourceSerializer(random_resource) # Response is a serialized JSON object
random_resource_id = random_resource.id # id of the random Resource for the game round
gameround = Gameround.objects.all().filter(taggings__resource_id=random_resource_id).order_by("?").first()
gameround_serializer = GameroundSerializer(gameround)
return Response({
'resource to coordinate': resource_serializer.data,
'gameround': gameround_serializer.data,
})
Related
I will try to explain my problem in English, because in fact I speak rather French.
I have a little problem and it's been 5 hours now that I can't solve it. I wrote three functions GET, POST and PUT. the first two work well but the PUT has a problem. I am looking to update my Roadtrip table. I send you my codes
# Research for Roadtrip class RoadtripView(CRUDView):
queryset = Roadtrip.objects.all().order_by("-id")
serializer_class=RoadtripSerializers
filter_backends = [DjangoFilterBackend]
filterset_fields = ['cfrom','gto','date']
# Get
def get(self,request,id=None):
if id:
return self.retrieve(request)
else:
return self.list(request)
# Post
def post(self, request, *args, **kwargs):
RoadtripSerializers.Meta.depth = 0
data = request.data
wrappers = super().create(request, *args, **kwargs)
RoadtripSerializers.Meta.depth = 1
return wrappers
# Update
def put(self,request,id,pk,*args,**kwargs):
kwargs['pk'] = self.request.id
RoadtripSerializers.Meta.depth = 0
user = request.user
if user != None:
data = request.data
roadid = Roadtrip.objects.get(id=pk)
submit = super().update(request, pk, *args, **kwargs)
return submits
My Model:
class Roadtrip(models.Model):
user = models.ForeignKey(User,on_delete=models.CASCADE, blank=True, null=True)
cfrom = models.CharField(max_length=200, blank=True,)
gto = models.CharField(max_length=200, blank=True,)
date= models.DateField(blank=True, null=True)
frontsits = models.CharField(max_length=200,blank=True,)
backsits = models.CharField(max_length=200, blank=True,)
hour = models.CharField(max_length=200, blank=True,)
created_at = models.DateField(auto_now_add=True)
def __str__(self):
return f"coming_from=={self.cfrom}<==>going_to:{self.gto}==Date=={self.date}==Hour=={self.hour}"
My URL
urlpatterns = [
path("", include(route.urls)),
path("roadtrip/",RoadtripView.as_view(),name="roadtrip"),
path("roadtrip/<int:id>/",RoadtripView.as_view(),name="roadtripdetal"),
path("booking/<int:id>",BookingView.as_view(),name="booking"),
path("booking/",BookingView.as_view(),name="booking"),
# path("contact/",ContactView.as_view(),name="contact"),
# path("contact/<int:id>",ContactView.as_view(),name="contact"),
path("payment/",PaymentView.as_view(),name="payment"),
path("addtobooking/",addtobooking.as_view(),name="addtobooking"),
]
Error on Swagger doc :
enter image description here
Change
def put(self,request,id,pk,*args,**kwargs):
to
def put(self,request,id,*args,**kwargs): # remove pk
I also suggest that you change id to pk everywhere.
I'm attempting to create a voting system where the score of a given post is separate from the type of votes placed by users. In the event that a user deletes a profile, a given score should not increment/decrement due to their vote being deleted. Therefore scores are only updated by using an F('score') + 1 or F('score') - 1 expression.
Within Vote.save(), I'm trying to implement this, yet the Question.score field doesn't update when the Vote is created. How can I get the test to pass where the score in the question goes from 0 to 1? django.db.models.F is in fact being imported into the module but it's not displayed here.
class TestQuestionScoreUpVote(TestCase):
'''Verify that a Question's score increments by one point
when the Vote is an "up" vote.'''
#classmethod
def setUpTestData(cls):
tag1 = Tag.objects.create(name="Tag1")
user = get_user_model().objects.create_user("TestUser")
profile = Profile.objects.create(user=user)
cls.question = Question.objects.create(
title="Question__001",
body="Content of Question 001",
profile=profile
)
cls.question.tags.add(tag1)
user_vote = Vote.objects.create(
profile=profile, type="upvote", content_object=cls.question
)
def test_new_user_vote_on_question(self):
self.assertEqual(self.question.score, 1)
class Post(Model):
body = TextField()
date = DateField(default=date.today)
comment = ForeignKey('Comment', on_delete=CASCADE, null=True)
profile = ForeignKey(
'authors.Profile', on_delete=SET_NULL, null=True,
related_name='%(class)ss',
related_query_name="%(class)s"
)
vote = GenericRelation(
'Vote', related_query_name="%(class)s"
)
score = IntegerField(default=0)
class Meta:
abstract = True
class Question(Post):
title = CharField(max_length=75)
tags = ManyToManyField(
'Tag', related_name="questions", related_query_name="question"
)
views = IntegerField(default=0)
objects = Manager()
postings = QuestionSearchManager()
class Meta:
db_table = "question"
ordering = ["-score" , "-date"]
def __repr__(self):
return f"{self.__class__.__name__}(title={self.title})"
class Vote(Model):
profile = ForeignKey(
'authors.Profile', on_delete=SET_NULL, null=True,
related_name="votes"
)
type = CharField(max_length=7)
content_type = ForeignKey(ContentType, on_delete=CASCADE)
object_id = PositiveIntegerField()
content_object = GenericForeignKey()
def save(self, *args, **kwargs):
post = ContentType.objects.get_for_id(
self.content_type_id
).get_object_for_this_type(id=self.object_id)
if self.type == "upvote":
post.score = F("score") + 1
else:
post.score = F("score") - 1
post.refresh_from_db()
super().save(*args, **kwargs)
You are forgetting to call post.save() after the object change:
def save(self, *args, **kwargs):
post = ContentType.objects.get_for_id(
self.content_type_id
).get_object_for_this_type(id=self.object_id)
if self.type == "upvote":
post.score = F("score") + 1
else:
post.score = F("score") - 1
post.save() # <- HERE
post.refresh_from_db()
super().save(*args, **kwargs)
Have two models - Program and Segments. I need to calculate the total times in the program entry from the fields within the associated Segments. I attempted to do that by overriding the save methods, but when entering a new segment it won't update the program model entries unless I go directly into the program form and save/update it.
I am missing how to get the segment Update to cause the Program Save/Update to happen.
How do I give it the context to call the program save method within the Segment update (After the segment has been saved).
Code of the models is:
from django.db import models
from django.urls import reverse
from datetime import datetime, timedelta
class Program(models.Model):
air_date = models.DateField(default="0000-00-00")
air_time = models.TimeField(default="00:00:00")
service = models.CharField(max_length=10)
block_time = models.TimeField(default="00:00:00")
block_time_delta = models.DurationField(default=timedelta)
running_time = models.TimeField(default="00:00:00")
running_time_delta = models.DurationField(default=timedelta)
remaining_time = models.TimeField(default="00:00:00")
remaining_time_delta = models.DurationField(default=timedelta)
title = models.CharField(max_length=190)
locked_flag = models.BooleanField(default=False)
deleted_flag = models.BooleanField(default=False)
library = models.CharField(null=True,max_length=190,blank=True)
mc = models.CharField(null=True,max_length=64)
producer = models.CharField(null=True,max_length=64)
editor = models.CharField(null=True,max_length=64)
remarks = models.TextField(null=True,blank=True)
audit_time = models.DateTimeField(null=True)
audit_user = models.CharField(null=True,max_length=32)
def calculate_time(self):
total_run_time_delta = timedelta(minutes=0)
for segs in self.segments.all():
total_run_time_delta += segs.length_time_delta
self.running_time_delta = total_run_time_delta
self.running_time = f"{self.running_time_delta}"
hold_time = self.block_time.strftime("%H:%M:%S")
t = datetime.strptime(hold_time,"%H:%M:%S")
self.block_time_delta = timedelta(hours=t.hour,
minutes=t.minute,seconds=t.second)
self.remaining_time_delta = self.block_time_delta - total_run_time_delta
self.remaining_time = f"{abs(self.remaining_time_delta)}"
def save(self, *args, **kwargs):
self.calculate_time()
super().save(*args,**kwargs)
def __str__(self):
return f"{self.pk} : {self.title}"
def get_absolute_url(self):
return reverse('program_detail', args=[str(self.id)])
#return reverse('program-update', kwargs={'pk': self.pk})
class Segment(models.Model):
program_id = models.ForeignKey(Program,
on_delete=models.CASCADE,
related_name='segments', #new link to Program
)
sequence_number = models.DecimalField(decimal_places=2,max_digits=6,default="0.00")
title = models.CharField(max_length=190)
bridge_flag = models.BooleanField(default=False)
length_time = models.TimeField(null=True,default=None, blank=True)
length_time_delta = models.DurationField(default=timedelta)
author = models.CharField(max_length=64,null=True,default=None,blank=True)
voice = models.CharField(max_length=64,null=True,default=None,blank=True)
library = models.CharField(max_length=190,null=True,default=None,blank=True)
summary = models.TextField()
audit_time = models.DateTimeField(null=True)
audit_user = models.CharField(null=True,max_length=32)
def save( self, *args, **kwargs):
super().save(*args,**kwargs)
return super(Program,self.program_id).save()
def __str__(self):
return f"{self.title}"
The views look like this...
class ProgramUpdateView(LoginRequiredMixin,UpdateView):
class Meta:
model = Program
widgets = {
'remarks': Textarea(attrs={'row':10, 'cols':80}),
}
model = Program
success_url = "/program/{id}/"
template_name = 'program_update.html'
fields = [
'title',
'service',
'library',
'air_date',
'air_time',
'producer',
'editor',
'mc',
'block_time',
'remaining_time',
'running_time',
'remarks',
]
def form_valid(self, form):
return super(ProgramUpdateView, self).form_valid(form)
class SegmentUpdate(LoginRequiredMixin,UpdateView):
model = Segment
fields = '__all__'
template_name = 'segment_update.html'
I originally thought I could do this all in the models, but now I am not so sure .
Thanks for any info you can provide....
try to directly call Program.save() method through the fk
in Segment model
def save( self, *args, **kwargs):
super().save(*args,**kwargs)
self.program_id.save()
or use django signals https://docs.djangoproject.com/en/3.1/topics/signals/
from django.db.models.signals import post_save, post_delete
#receiver([post_save, post_delete], sender=Segment)
def update_program(sender, instance, **kwargs):
program = Program.objects.get(pk=instance.program_id.pk)
program.save()
Please keep your database atomic. Don't save in it something that can be computed from other fields unless you have a very good reason to do it. The reason you're giving for doing it doesn't seem like a good one.
You want the total time of the segments when you got a list of programs ? Fine, simply annotate the querystring with a sum. You'll do it everytime ? Create a custom queryset/manager that do it for you.
I am using python ebay-sdk library for connection to ebay api.
I want to get some cases from ebay api and save it to the database.
I have one app and models like this:
class Case(models.Model):
# case is not necessarily a listing (often O2M)
STATUS = Choices('new', 'cancelled', 'active', 'taken_down', 'archived')
name = models.CharField(max_length=512)
when_created = models.DateTimeField(default=now, blank=True)
user = models.ForeignKey(User)
make = models.ForeignKey(Make)
status = StatusField(default=STATUS.new)
platform = models.ForeignKey(Platform, on_delete=models.CASCADE)
listing_id = models.CharField(max_length=64)
listing_owner = models.CharField(max_length=128)
url = models.CharField(max_length=512)
price = models.FloatField(blank=True, null=True)
# for now currency as char is ok.
currency = models.CharField(max_length=3, default='USD')
quantity = models.IntegerField(default=1)
when_listing_started = models.DateTimeField(blank=True, null=True)
when_listing_ends = models.DateTimeField(blank=True, null=True)
valid_days = models.IntegerField(blank=True, null=True)
objects = models.Manager.from_queryset(CaseQueryset)()
def __str__(self):
return self.name
#property
def days_valid(self):
created = self.when_created.replace(tzinfo=None)
now = datetime.datetime.utcnow().replace(tzinfo=None)
datetime.timedelta(6, 1)
return (now - created).days
def handle_sent_report(self):
self._set_active_state()
def handle_cancel(self):
self._set_cancelled_state()
def _set_active_state(self):
if self.status != Case.STATUS.new:
raise InvalidCaseStatus
self.status = Case.STATUS.active
self.save()
def _set_cancelled_state(self):
if self.status not in (Case.STATUS.new, Case.STATUS.active):
raise InvalidCaseStatus
self.status = Case.STATUS.cancelled
self.save()
And I created other app ebay-finder and ebay_find.py to find cars in eBay:
from ebaysdk.finding import Connection as Finding
from django.db import models
from cases.models import Case
from trademarks.models import Make
def ebay_find():
pass
api = Finding(domain='svcs.sandbox.ebay.com', appid="MY_EBAY_APP_ID", config_file=None)
response = api.execute('findItemsAdvanced', {'keywords': 'Cadillac'})
items = response.dict()
items_list = items['searchResult'].get('item')
ebay_cases = []
for item in items_list:
new_case = Case.objects.create(
name=item['title'],
platform="Ebay",
listing_id=car["model"],
url=item['viewItemURL'],
price=item['sellingStatus']['currentPrice']['value'],
currency=item['sellingStatus']['currentPrice']['_currencyId']
)
new_case.save()
I'm confused how to add this data from items_list to my database like in Case. I must take all the same fields like in Case? Can I have some fields default on create object?
If your want your data to be saved with the least hassle, then yes, use the same field-names.
You have an example of a model field with a default in your own code:
currency = models.CharField(max_length=3, default='USD')
You could also override your model's save() method:
def save(self, *args, **kwargs):
super(Case, self).save(*args, **kwargs)
if not self.when_listing_started:
self.when_listing_started = datetime.datetime.now()
super(Case, self).save(*args, **kwargs)
How long should this take to run?
query = Contact.objects.filter(contact_owner=batch.user, subscribed=True)
objs = [
Message(
recipient_number=e.mobile,
content=content,
sender=e.contact_owner,
billee=user,
sender_name=sender,
)
for e in query
Just this code nothing else (not saving to db I'm only running above i.e. create the objects). It takes 15 mins for 5000 messages objects to be created in the query. Is this right? Why is Django so slow?
This is the model it creates, again I'm not saving here. I think there ahs to be an issue in the model when an object is created, that or Django is just too slow for my needs.
Model message
from django.db import models
from django.contrib.contenttypes import generic
from django.utils.translation import ugettext as _
from django.conf import settings
from django.db.models.signals import post_save, pre_save
from django.dispatch import receiver
import uuidfield.fields
import picklefield
import jsonfield
if 'timezones' in settings.INSTALLED_APPS:
from timezones.utils import adjust_datetime_to_timezone
else:
def adjust_datetime_to_timezone(a, b, c):
return a
from gateway import Gateway
class MessageManager(models.Manager):
def get_matching_message(self, datadict):
for gateway in Gateway.objects.all():
try:
return Message.objects.get(
gateway_message_id=datadict.get(gateway.status_msg_id),
gateway=gateway,
)
except Message.DoesNotExist:
pass
def get_original_for_reply(self, datadict):
for gateway in Gateway.objects.all():
try:
return Message.objects.get(
uuid=datadict.get(gateway.uuid_keyword),
gateway=gateway
)
except Message.DoesNotExist:
pass
# This may have been a message sent from another phone, but
# there may be a reply-code that was added in.
return self.custom_reply_matcher(datadict)
def custom_reply_matcher(self, datadict):
# Designed to be overridden.
return None
def get_last_rate_for(self, recipient_number):
m = Message.objects.filter(recipient_number=recipient_number).exclude(
gateway_charge=None).order_by('-send_date')[0]
return m.gateway_charge / m.length
def get_message(self, gateway_message_id):
try:
return Message.objects.get(gateway_message_id=gateway_message_id,)
except Message.DoesNotExist:
pass
MESSAGE_STATUSES = (
('Unsent', 'Unsent'),
('Sent', 'Sent'),
('Delivered', 'Delivered'),
('Failed', 'Failed'),
)
class Message(models.Model):
"""
A Message.
We have a uuid, which is our reference. We also have a gateway_message_id,
which is their reference. This is required by some systems so we can
pass in a unique value that will allow us to match up replies to original
messages.
"""
content = models.TextField(help_text=_(u'The body of the message.'))
recipient_number = models.CharField(max_length=32,
help_text=_(u'The international number of the recipient'
', without the leading +'))
sender = models.ForeignKey('auth.User', related_name='sent_sms_messages')
sender_name = models.CharField(max_length=11)
send_date = models.DateTimeField(null=True, blank=True, editable=False)
delivery_date = models.DateTimeField(null=True, blank=True, editable=False,
help_text="The date the message was sent.")
uuid = uuidfield.fields.UUIDField(auto=True,
help_text=_(u'Used for associating replies.'))
status = models.CharField(max_length=16, choices=MESSAGE_STATUSES,
default="Unsent",
)
status_message = models.CharField(max_length=128, null=True, blank=True)
billed = models.BooleanField(default=False)
content_type = models.ForeignKey('contenttypes.ContentType')
object_id = models.PositiveIntegerField()
billee = generic.GenericForeignKey()
gateway = models.ForeignKey('sms.Gateway',
null=True, blank=True, editable=False)
gateway_message_id = models.CharField(max_length=128,
blank=True, null=True, editable=False)
reply_callback = picklefield.PickledObjectField(null=True, blank=True)
gateway_charge = models.DecimalField(max_digits=10, decimal_places=5,
null=True, blank=True)
charge = models.DecimalField(max_digits=10, decimal_places=5,
null=True, blank=True)
objects = MessageManager()
class Meta:
app_label = 'sms'
permissions = (
('view_message', 'Can view message'),
)
ordering = ('send_date',)
def send(self, gateway):
gateway.send(self)
#property
def length(self):
"""Unicode messages are limited to 70 chars/message segment."""
# try:
# return len(str(self.content)) / 160 + 1
# except UnicodeEncodeError:
# return len(self.content) / 70 + 1
return len(self.content) / 160 + 1
#property
def local_send_time(self):
# TODO: Get this from UserProfile?
if getattr(self.billee, 'timezone', None):
return adjust_datetime_to_timezone(
self.send_date,
settings.TIME_ZONE,
self.billee.timezone
)
return self.send_date
#property
def local_send_date(self):
return self.local_send_time.date()
def __unicode__(self):
return "[%s] Sent to %s by %s at %s [%i]" % (
self.status,
self.recipient_number,
self.sender,
self.send_date,
self.length
)
#receiver(pre_save, sender=Message)
def my_handler(sender, **kwargs):
instance = kwargs['instance']
if not instance.charge:
instance.charge = instance.length
# No need to save, as we're slipping the value in
# before we hit the database.
contact model
import os
import datetime
from uuid import uuid4
from datetime import date
from django.db import models
from django.core.urlresolvers import reverse
from django.contrib.auth.models import User
from django.utils.translation import ugettext as _
from django.utils import timezone
from django.db.models.signals import pre_delete
from django.dispatch.dispatcher import receiver
from adaptor.fields import *
from adaptor.model import CsvModel
def path_and_rename(path):
"""
Callable function for renaming the file being uploaded.
"""
def wrapper(instance, filename):
ext = filename.split('.')[-1]
# get filename
if instance.pk:
filename = '{}.{}'.format(instance.pk, ext)
else:
# set filename as random string
filename = '{}.{}'.format(uuid4().hex, ext)
# return the whole path to the file
return os.path.join(path, filename)
return wrapper
class GroupManager(models.Manager):
def for_user(self, user):
return self.get_query_set().filter(user=user, )
class Group(models.Model):
"""
Stores all groups.
"""
name = models.CharField(max_length=60)
modified = models.DateTimeField(null=True, auto_now=True, help_text="Shows when object was modified.")
created = models.DateTimeField(auto_now_add=True, help_text="Shows when object was created.")
#FK
user = models.ForeignKey(User, related_name="user")
objects = GroupManager()
def __unicode__(self):
return self.name
def get_absolute_url(self):
return reverse('contacts.views.group', args=[str(self.id)])
def get_delete_url(self):
return reverse('contacts.views.group_delete_confirm', args=[str(self.id)])
class ContactManager(models.Manager):
"""
Custom Manager for keyword.
"""
def unsorted_contacts(self, user):
"""
Manager that will list all records for a user where group is 'None'.
"""
return self.get_query_set().filter(contact_owner=user, group=None)
def for_user_and_group(self, user, group):
"""
Manager that will list all records for a user where group is 'group'.
"""
return self.get_query_set().filter(contact_owner=user, group=group)
def for_user(self, user):
"""
Manager that will list all records for a user they own.
"""
return self.get_query_set().filter(contact_owner=user)
class Contact(models.Model):
"""
Stores all contacts.
"""
first_name = models.CharField(max_length=60, blank=True)
last_name = models.CharField(max_length=60, blank=True)
company = models.CharField(max_length=100, blank=True)
mobile = models.CharField(max_length=15)
email = models.EmailField(max_length=100, blank=True)
subscribed = models.NullBooleanField(default=1, help_text="Shows if contact is unsubscribed to SMS/Email.")
modified = models.DateTimeField(null=True, auto_now=True, help_text="Shows when object was modified.")
created = models.DateTimeField(auto_now_add=True, help_text="Shows when object was created.")
objects = ContactManager()
#FK
group = models.ForeignKey(Group, related_name='contacts', blank=True, null=True)
contact_owner = models.ForeignKey(User)
def __unicode__(self):
return self.first_name
def full_name(self):
return "%s %s" % (self.first_name, self.last_name)
def get_delete_url(self):
return reverse('contacts.views.contact_delete', args=[str(self.id), str(self.group_id)])
def get_group_absolute_url(self):
return reverse('contacts.views.group', args=[str(self.group_id)])
#property
def user(self):
return self.contact_owner
#receiver(pre_delete, sender=Contact)
def contact_cleanup(sender, instance, **kwargs):
"""
Do a bit of tidying up when deleting a Contact.
Sent at the beginning of a model's delete() method and a queryset's delete() method.
"""
# Remove any FK's not done by cascade delete like generic relationships.
from unsubscribe.models import Unsubscribe
unsubscribe_list = Unsubscribe.objects.filter(object_id=instance.id, content_type__model='contact')
unsubscribe_list.delete()
class Upload(models.Model):
"""
Stores jobs and status uploads of file uploads for CSV import.
"""
filepath = models.FileField(upload_to=path_and_rename('uploadsCSV'),
help_text="It can take several minutes for contacts to appear.")
# Upload audit information
uploaded_by = models.ForeignKey(User)
date_uploaded = models.DateTimeField(auto_now_add=True)
# Processing audit information
PENDING, PROCESSED, FAILED = 'Pending', 'Processed', 'Failed'
STATUSES = (
(PENDING, _(PENDING)),
(PROCESSED, _(PROCESSED)),
(FAILED, _(FAILED)),
)
status = models.CharField(max_length=64, choices=STATUSES, default=PENDING)
processing_description = models.TextField(blank=True, null=True)
num_records = models.PositiveIntegerField()
num_columns = models.PositiveIntegerField()
date_start_processing = models.DateTimeField(null=True)
date_end_processing = models.DateTimeField(null=True)
#FKs
group = models.ForeignKey(Group)
def get_configurator_url(self):
return reverse('contacts.views.upload_configurator', args=[str(self.id)])
def process(self, cleaned_data):
self.date_start_processing = timezone.now()
try:
group_position = self.num_columns + 1
upload_id_position = self.num_columns + 1
# Try and import CSV
import_this(data=self.filepath, extra_fields=[
{'value': self.group_id, 'position': group_position},
{'value': self.uploaded_by.id, 'position': upload_id_position}], cleaned_data=cleaned_data)
self._mark_processed(self.num_records)
except Exception as e:
self._mark_failed(unicode(e))
def was_processing_successful(self):
return self.status == self.PROCESSED
def was_processing_successful(self):
return self.status == self.PROCESSED
def _mark_processed(self, num_records, description=None):
self.status = self.PROCESSED
self.date_end_processing = date.today()
self.num_records = num_records
self.processing_description = description
self.save()
def _mark_failed(self, description):
self.status = self.FAILED
self.processing_description = description
self.save()
def import_this(cleaned_data, *args, **kw):
# make custom ContactCSVModel
class ContactCSVModel(CsvModel):
for k, v in cleaned_data.items():
if not v == '':
# print("---------------------------------")
# print(str(v))
# print("---")
# print(k[3:])
# print("---------------------------------")
setattr(CsvModel, v, CharField(row_num=k[3:]))
group = DjangoModelField(Group, row_num="5")
contact_owner = DjangoModelField(User, row_num="6")
class Meta:
delimiter = ","
dbModel = Contact
update = {'keys': ["mobile", "group"]}
return ContactCSVModel.import_data(*args, **kw)
If you install Django debugtoolbar you could actually see the queries being fired, and the code responsible on firing them.
This slowness is mainly because django fires a new db query every time you do e.mobile and e.contact_owner, in the loop.
To prevent these queries, prefetch the data using select_related like below
query = Contact.objects.select_related('mobile', 'contact_owner').filter(contact_owner=batch.user, subscribed=True)
If your relations are many to many, then use prefetch_related rather than select_related.