Create template object to display dependent on time of day - django

I need to create a cross-site template object which will check the current time and return either one string if it's within a time range specified in a model or another or blank in all other cases.
Seems simple, but I wonder what is the best approach here? Also, there are a few other considerations:
the string should be editable
the display time should not have an end time before the start time
we need to allow for multiple strings to be stored, but only one (or none) to be selected as 'live'
As a starting point, I'd use a model to define the string as well as the start and end time, like this:
from datetime import datetime
class dynamicString(models.Model):
start = models.TimeField()
end = models.TimeField()
name = models.CharField(max_length=50, help_text = 'Just for reference, won\'t be displayed on site.')
number = models.CharField(max_length=18, help_text = 'This is the string to be displayed within the above timeframe.')
active = models.BooleanField(help_text = 'Uncheck this to stop string from displaying entierly.')
def __unicode__(self):
return self.name
But where next to incorporate the logic rules?

Redesigned this using a simpler rule system: check for one number time range, if within range and if not blank, return the number, otherwise return a backup number (which may be blank).
Works a treat passed as a context process.

Related

Creating an object with a field based on the last one in django

Consider the following Model:
class Tickets(models.Model):
number = models.PositiveIntegerField(default=0)
created_at = models.DateTimeField(auto_now_add=True)
and the following method that creates a ticket:
#transaction.atomic()
def create_ticket(self):
last = Tickets.objects.order_by('created_at').last()
next_number = 1 if last is None else last.number + 1
new_ticket = Tickets.objects.create(number=next_number)
return new_ticket
This fails if create_ticket is called concurrently, as the transaction won't have a reason to abort (no same record has been updated by multiple calls) but last will return repeated results on some runs.
I'm aware of auto incrementing fields and they're not an option in this case, as there is extra logic in place that may cause next_number to reset to 0. I'm leaning towards finding a way to acquire a table lock but was hoping to find an easier way.

Django ORM - LEFT JOIN with WHERE clause

I have made a previous post related to this problem here but because this is a related but new problem I thought it would be best to make another post for it.
I'm using Django 1.8
I have a User model and a UserAction model. A user has a type. UserAction has a time, which indicates how long the action took as well as a start_time which indicates when the action began. They look like this:
class User(models.Model):
user_type = models.IntegerField()
class UserAction:
user = models.ForeignKey(User)
time = models.IntegerField()
start_time = models.DateTimeField()
Now what I want to do is get all users of a given type and the sum of time of their actions, optionally filtered by the start_time.
What I am doing is something like this:
# stubbing in a start time to filter by
start_time = datetime.now() - datetime.timedelta(days=2)
# stubbing in a type
type = 2
# this gives me the users and the sum of the time of their actions, or 0 if no
# actions exist
q = User.objects.filter(user_type=type).values('id').annotate(total_time=Coalesce(Sum(useraction__time), 0)
# now I try to add the filter for start_time of the actions to be greater than or # equal to start_time
q = q.filter(useraction__start_time__gte=start_time)
Now what this does is of course is an INNER JOIN on UserAction, thus removing all the users without actions. What I really want to do is the equivalent of my LEFT JOIN with a WHERE clause, but for the life of me I can't find how to do that. I've looked at the docs, looked at the source but am not finding an answer. I'm (pretty) sure this is something that can be done, I'm just not seeing how. Could anyone point me in the right direction? Any help would be very much appreciated. Thanks much!
I'm having the same kind of problem as you. I haven't found any proper way of solving the problem yet, but I've found a few fixes.
One way would be looping through all the users:
q = User.objects.filter(user_type=type)
for (u in q):
u.time_sum = UserAction.filter(user=u, start_time__gte=start_time).aggregate(time_sum=Sum('time'))['time_sum']
This method does however a query at the database for each user. It might do the trick if you don't have many users, but might get very time-consuming if you have a large database.
Another way of solving the problem would be using the extra method of the QuerySet API. This is a method that is detailed in this blog post by Timmy O'Mahony.
valid_actions = UserAction.objects.filter(start_time__gte=start_time)
q = User.objects.filter(user_type=type).extra(select={
"time_sum": """
SELECT SUM(time)
FROM userAction
WHERE userAction.user_id = user.id
AND userAction.id IN %s
""" % (%s) % ",".join([str(uAction.id) for uAction in valid_actions.all()])
})
This method however relies on calling the database with the SQL table names, which is very un-Django - if you change the db_table of one of your databases or the db_column of one of their columns, this code will no longer work. It though only requires 2 queries, the first one to get the list of valid userAction and the other one to sum them to the matching user.

Django - filter queryset disregarding spaces

I have a model that contains phone numbers external_number, stored as a char field:
models.py
class PhoneRecord(models.Model):
def __unicode__(self):
return "Call to %s (%s)" % (self.external_number, self.call_date.strftime("%c"))
INBOUND = "I"
OUTBOUND = "O"
DIRECTION_CHOICES = (
(INBOUND, "Inbound"),
(OUTBOUND, "Outbound"),
)
external_number = models.CharField(max_length=20)
call_date = models.DateTimeField()
external_id = models.CharField(max_length=20)
call_duration = models.TimeField()
call_direction = models.CharField(max_length=1, choices=DIRECTION_CHOICES, default=INBOUND)
call = models.FileField(upload_to='calls/%Y/%m/%d')
The form is cleaning and storing the data using the UKPhoneNumberField from https://github.com/g1smd/django-localflavor-UK-forms/blob/master/django/localflavor/uk/forms.py
This means that the number is stored in the database in the format 01234 567890 i.e. with a space in.
I have created a filter using django-filters which works well, when searching for partial phone number except that it doesn't filter correctly when the search term doesn't include the space. i.e.
search for 01234 returns the record for the example above
search for 567890 returns the record for the example above
search for 01234 567890 returns the record for the example above
search for 01234567890 does not return the record for the example above
Now, I could subject the form on the filter to the same restrictions (i.e. UKPhoneNumberField as the input screen, but that then takes away the ability to search for partial phone numbers.
I have also explored the possibility of using something like django-phonenumber-field that will control both the model and the form, but the validation provided by UKPhoneNumberField allows me to validate based on the type of number entered (e.g. mobile or geographical).
What I would ideally like is either
My filter to ignore spaces that are either input by the user in their search query, or any spaces that are in the stored value. Is this even possible?
Apply the validation provided by UKPhoneNumberField to another field type without having to analyse and re-write all the regular expressions provided.
Some other UK Phone number validation I have not found yet!
You could setup the filter to do something like this:
phone_number = "01234 567890"
parts = phone_number.split(" ")
PhoneRecord.objects.filter(
external_number__startswith="{} ".format(parts[0]),
external_number__endswith=" {}".format(parts[1]),
)
That way the filter is looking for the first half of the number with the space and then the second half of the number with the space as well. The only records that would be returned would be ones that had the value of "01234 567890"
I ended up adding a custom filter with a field_class = UKPhoneFilter. This means I can't search on partial numbers, but that was removed as a requirement from the project.
You should think about normalizing the data before you save them into your DB.
You could just use django-phonenumber-field which does just that.
In regarding your problem you can always use django's regex field postfix to query over regular expression.
e.g. MyModel.objects.filter(myfiel__regex=r'[a-Z]+')

Django wipe datefield value from view

Bit of a random question but ill try my best to describe what im trying to do. I am building a app to manage a set of physical assets which get loaned out.
To return an asset the user visits /return/1/ which clears the name of the user, date borrowed, date returned etc
view.py
def returnlaptop(request, laptop_pk):
Laptops.objects.filter(pk=laptop_pk).update(laptop_status='In')
Laptops.objects.filter(pk=laptop_pk).update(user='')
Laptops.objects.filter(pk=laptop_pk).update(borrowed_date='')
Laptops.objects.filter(pk=laptop_pk).update(return_date='')
return HttpResponseRedirect('/')
This works well except for when i try and update the values in the models.datefield
[u"' ' value has an invalid date format. It must be in YYYY-MM-DD format."]
Is there anyway around this? or am I going about this the completely wrong way?
Cheers
Xcom
I'm not 100% sure but I think that hits the database 4 times...
The first issue is that update is meant for use on a queryset. You are filtering on the primary key so you are only getting 1 object back. Which means that you should use get instead like this
laptop = Laptops.objects.get(pk=laptop_pk)
and now you can use that to properly fetch the object from the database, modify it, and save it like so
laptop = Laptops.objects.get(pk=laptop_pk)
laptop.laptop_status = 'In'
laptop.user = ''
...
laptop.save()
which would only hit the database 1 time.
The final issue is that you are attempting to set a date to an empty string. That won't work because it is expecting a date object. One thing you can do is modify your model so that the dates can be blank and so that the database accepts null values.
class Laptops(models.Model):
...
borrowed_date = models.DateField(null=True, blank=True)
return_date = models.DateField(null=True, blank=True)
The other thing you can do is use the minimum date can be accessed with timezone.datetime.min

Django, accessing PostgreSQL sequence

In a Django application I need to create an order number which looks like: yyyymmddnnnn in which yyyy=year, mm=month, dd=day and nnnn is a number between 1 and 9999.
I thought I could use a PostgreSQL sequence since the generated numbers are atomic, so I can be sure when the process gets a number that number is unique.
So I created a PostgreSQL sequence:
CREATE SEQUENCE order_number_seq
INCREMENT 1
MINVALUE 1
MAXVALUE 9999
START 1
CACHE 1
CYCLE;
This sequence can be accessed as a tables having one row. So in the file checkout.py I created a Django model to access this sequence.
class OrderNumberSeq(models.Model):
"""
This class maps to OrderNumberSeq which is a PostgreSQL sequence.
This sequence runs from 1 to 9999 after which it restarts (cycles) at 1.
A sequence is basically a special single row table.
"""
sequence_name = models.CharField(max_length=128, primary_key=True)
last_value = models.IntegerField()
increment_by = models.IntegerField()
max_value = models.IntegerField()
min_value = models.IntegerField()
cache_value = models.IntegerField()
log_cnt = models.IntegerField()
is_cycled = models.BooleanField()
is_called = models.BooleanField()
class Meta:
db_table = u'order_number_seq'
I set the sequence_name as primary key as Django insists on having a primary key in a table.
The I created a file get_order_number.py with the contents:
def get_new_order_number():
order_number = OrderNumberSeq.objects.raw("select sequence_name, nextval('order_number_seq') from order_number_seq")[0]
today = datetime.date.today()
year = u'%4s' % today.year
month = u'%02i' % today.month
day = u'%02i' % today.day
new_number = u'%04i' % order_number.nextval
return year+month+day+new_number
now when I call 'get_new_order_number()' from the django interactive shell it behaves as expected.
>>> checkout.order_number.get_new_order_number()
u'201007310047'
>>> checkout.order_number.get_new_order_number()
u'201007310048'
>>> checkout.order_number.get_new_order_number()
u'201007310049'
You see the numbers nicely incrementing by one every time the function is called. You can start multiple interactive django sessions and the numbers increment nicely with no identical numbers appearing in the different sessions.
Now I try to use call this function from a view as follows:
import get_order_number
order_number = get_order_number.get_new_order_number()
and it gives me a number. However next time I access the view, it increments the number by 2. I have no idea where the problem is.
The best solution I can come up with is: don't worry if your order numbers are sparse. It should not matter if an order number is missing: there is no way to ensure that order numbers are contiguous that will not be subject to a race condition at some point.
Your biggest problem is likely to be convincing the pointy-haired ones that having 'missing' order numbers is not a problem.
For more details, see the Psuedo-Key Neat Freak entry in SQL Antipatterns. (note, this is a link to a book, which the full text of is not available for free).
Take a look at this question/answer Custom auto-increment field in postgresql (Invoice/Order No.)
You can create stored procedures using RawSql migration.