Flask / Automatic Database field change on True depending on the date - flask

How to make expired default in False and automatic setting of the expired to True, depending on whether the date has passed from the field expire_date?
My model.py
class PlanPurchase(db.Model):
plan_id = db.Column(db.Integer, db.ForeignKey('plan.plan_id'))
user_id = db.Column(db.Integer, db.ForeignKey('user.user_id', ondelete='CASCADE'))
expire_date = db.Column(db.DateTime)
expired = db.Column(db.Boolean)
def __init__(self, plan_id, user_id, expire_date, total_cost=0, expired=True):
self.plan_id = plan_id
self.user_id = user_id
self.expire_date = expire_date
self.expired = expired
Example.
expire_date = 2020-02-02 . expired should be True.
expire_date = 2020-12-12 . expired should be False.
It should be automatic. Compare current date and date in expire_date

There are two approaches for this.
If you have some kind of login system and user has to login to interact with PlanPurchase, then you can use before_request flask decorator to check for expiration and update the field. The before_requestcallback is always run before your actual endpoint request.
#app.before_request
def before_request_func():
if session.get('logged_in'):
""""
Check for expiration
and update expire field accordingly.
"""
#app.route("/login")
def login():
"""
Your login logic goes here.
"""
session['logged_in'] = True
You can use background task like celery or even configure cron jobs to check for expiration logic every midnight and update accordingly.
It depends upon your application architecture on which method you should use.
Tip:
Seems like you are using flask_sqlalchemy. You don't need the __init__ to pass parameters to the DB model. You can discard it.
class PlanPurchase(db.Model):
plan_id = db.Column(db.Integer, db.ForeignKey('plan.plan_id'))
user_id = db.Column(db.Integer, db.ForeignKey('user.user_id', ondelete='CASCADE'))
expire_date = db.Column(db.DateTime)
expired = db.Column(db.Boolean)
purhcase = PlanPurchase(plan_id=1,user_id=1,expired_date=datetime.now(),expired=False)
The above code works fine.

Related

How to make user inactive if not active for 1 day using django fsm

Need to change user status to inactive if not active for 1 day using django fsm library
class UserValidity(models.Model):
state = FSMField(default="active")
name = models.CharField(max_length=10)
date = models.DateField(auto_now_add=True)
def state_inactive(self):
if self.date < datetime.date.today():
self.state = 'inactive'
return self.save()
else:
return True
#transition(field=state, source="active", target="inactive", conditions=[state_inactive])
def state_change(self):
print('State Inactive')
The FSMField field is an identifier here, you will be able to check user status from this field but you need to change it based on the condition.
One of the best solutions is you can run a scheduler that will run at 12:00 AM (Midnight) and this will run a function that will change the FSMField after comparing a datetime field, this datetime field will store the last date and time the user made a request to the server.

Checking for overlapping TimeField ranges

I have this model:
class Task(models.Model):
class Meta:
unique_together = ("campaign_id", "task_start", "task_end", "task_day")
campaign_id = models.ForeignKey(Campaign, on_delete=models.DO_NOTHING)
playlist_id = models.ForeignKey(PlayList, on_delete=models.DO_NOTHING)
task_id = models.AutoField(primary_key=True, auto_created=True)
task_start = models.TimeField()
task_end = models.TimeField()
task_day = models.TextField()
I need to write a validation test that checks if a newly created task time range overlaps with an existing one in the database.
For example:
A task with and ID 1 already has a starting time at 5:00PM and ends at 5:15PM on a Saturday. A new task cannot be created between the first task's start and end time. Where should I write this test and what is the most efficent way to do this? I also use DjangoRestFramework Serializers.
When you receive the form data from the user, you can:
Check the fields are consistent: user task_start < user task_end, and warn the user if not.
Query (SELECT) the database to retrieve all existing tasks which intercept the user time,
Order the records by task_start (ORDER BY),
Select only records which validate your criterion, a.k.a.:
task_start <= user task_start <= task_end, or,
task_start <= user task_end <= task_end.
warn the user if at least one record is found.
Everything is OK:
Construct a Task instance,
Store it in database.
Return success.
Implementation details:
task_start and task_end could be indexed in your database to improve selection time.
I saw that you also have a task_day field (which is a TEXT).
You should really consider using UTC DATETIME fields instead of TEXT, because you need to compare date AND time (and not only time): consider a task which starts at 23:30 and finish at 00:45 the day after…
This is how I solved it. It's not optimal by far, but I'm limited to python 2.7 and Django 1.11 and I'm also a beginner.
def validate(self, data):
errors = {}
task_start = data.get('task_start')
task_end = data.get('task_end')
time_filter = Q(task_start__range=[task_start, task_end])
| Q(task_end__range=[task_start, task_end])
filter_check = Task.objects.filter(time_filter).exists()
if task_start > task_end:
errors['error'] = u'End time cannot be earlier than start time!'
raise serializers.ValidationError(errors)
elif filter_check:
errors['errors'] = u'Overlapping tasks'
raise serializers.ValidationError(errors)
else:
pass
return data

Tweet Scheduler based on Flask Megatutorial: Issues with getting fields of flask-sqlalchemy databse

I'm in the process of modifying the Flask app created in following along Miguel Grinberg's Flask Mega Tutorial such that it is possible to post tweets. I have imported tweepy for accessing the twitter api and modified the databases to hold the scheduled time of a tweet.
I wish to iterate over the current_user's posts and the corresponding times from the SQLAlchemy database and post when the current time matches the scheduled time.
The database model modifications in model.py are as follows:
class Post(db.Model):
id = db.Column(db.Integer, primary_key = True)
body = db.Column(db.String(140))
timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)
socialnetwork = db.Column(db.String(40))
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
#This is the stuff for scheduling, just date
hour = db.Column(db.Integer)
minute = db.Column(db.Integer)
day = db.Column(db.Integer)
month = db.Column(db.Integer)
year = db.Column(db.Integer)
ampm = db.Column(db.String(2))
Just as a test, I wanted to iterate over the current user's posts and tweet them using tweepy:
#app.before_first_request
def activate_job():
def run_job():
posts = current_user.followed_posts().filter_by(socialnetwork ='Twitter')
for post in posts:
tweepy_api.update_status(message)
time.sleep(30)
thread = threading.Thread(target=run_job)
thread.start()
However, this returned the error:
AttributeError: 'NoneType' object has no attribute 'followed_posts'
on the terminal. This is perplexing me as I have used current_user multiple times in the same file to filter the posts by social network.
As in the following case in routes.py
#app.route('/<username>')
#login_required
def user(username):
user = User.query.filter_by(username = username).first_or_404()
socialnetwork = request.args.get("socialnetwork")
if socialnetwork == 'Facebook':
posts = current_user.followed_posts().filter_by(socialnetwork = 'Facebook')
elif socialnetwork == 'Twitter':
posts = current_user.followed_posts().filter_by(socialnetwork = 'Twitter')
else:
posts = current_user.followed_posts()
return render_template('user.html', user = user, posts = posts, form = socialnetwork)
The above yields no error and works perfectly.
If anyone could shed some light on what I am doing wrong, I'd be truly grateful.
You're likely running into issues because you're trying to get current_user on a different thread (see the Flask docs for more details). You're calling run_job() in a different context that doesn't have any current user (because there's no active request).
I'd rework it so that you get the current user's posts on the main thread (i.e. in activate_job(), then pass the list of posts to the background process to work on.
Something like:
def activate_job():
posts = current_user.followed_posts().filter_by(socialnetwork ='Twitter')
def run_job(posts):
for post in posts:
tweepy_api.update_status(message)
time.sleep(30)
thread = threading.Thread(target=run_job, args=[posts])
thread.start()
It's also worth noting that you may want to rethink your overall approach. Rather than checking with each request if there are any scheduled tweets to send, you should use some sort of background task queue that an operate independently of the web process. That way, you're not checking redundantly on each request, and you're not dependant on the user making requests around the scheduled time.
See The Flask Mega-Tutorial Part XXII: Background Jobs for more details, and look into Celery.

Turbogears2 AdminController throwing an error with a many-to-many relationship

I'm having an issue with turbogears admin controller throwing an error when I try to edit the User, ShoppingItem or ShoppingList items (code below). The error coming up is AttributeError: 'function' object has no attribute 'primary_key'. The Local Variables in frame always come back as the same:
mapper
<Mapper at 0x3719810; ShoppingList>
fields
['id']
self
<sprox.sa.provider.SAORMProvider instance at 0x03E537B0>
value
<bound method OrderedProperties.items of <sqlalchemy.util._collections.OrderedProperties object at 0x037199F0>>
entity
<class 'insertmealhere.model.shoppinglist.ShoppingList'>
field_name
'items'
I'm having trouble figuring out what is different between this and the other many-to-many relationships that are configured elsewhere in the code and are not throwing this error. I'm running Turbogears 2.2 on Python 2.7.8 currently on a windows 8.1 system. Any help is greatly appreciated.
list_item_table = Table("list_item_table", metadata,
Column('item_id', Integer, ForeignKey('shopping_item.id', onupdate="CASCADE", ondelete="CASCADE"), primary_key=True),
Column('list_id', Integer, ForeignKey('shopping_list.id', onupdate="CASCADE", ondelete='CASCADE'), primary_key=True))
class ShoppingItem(DeclarativeBase):
__tablename__ = "shopping_item"
id = Column(Integer, primary_key=True)
name = Column(String(50))
quantity = Column(String(5))
measure = Column(String(10))
# less important optional parameters that will be useful for users
brand = Column(String(50))
list_id = Column(Integer, ForeignKey('shopping_list.id'))
shopping_list = relation("ShoppingList", secondary=list_item_table, backref="items")
def get_owner_id(self):
return self.list.user_id
#classmethod
def delete_list(cls, id, user_id):
item = DBSession.query(cls).filter_by(id=id).one() # get the item from the given ID
if item.get_owner_id() == user_id: # owned by current user
DBSession.delete(item) # delete from shopping list
return True
flash(_("You do not have authorization to perform that action."))
return False
class ShoppingList(DeclarativeBase):
__tablename__ = 'shopping_list'
id = Column(Integer, primary_key=True)
date = Column(Date, index=True, nullable=False)
static = Column(Boolean, nullable=False, default=False)
# static is true if the items from the meal plan have been imported into the shopping list. Once done you can edit
# the items in the shopping list, remove items, etc. Until the shopping list is made static it is impossible to edit
# the items that are imported from the schedule as they do not exist in the shopping list! (and we do not want to
# edit them in the recipe!
user_id = Column(Integer, ForeignKey('tg_user.user_id'))
user = relation("User", backref="shopping_lists")
date_user_list = Index('date_user_list', 'date', 'user_id')
Maybe it's the list_id = Column(Integer, ForeignKey('shopping_list.id')) in the ShoppingItem model class that's confusing SQLAlchemy?

Reevaluating a model-level query

In brief: A model's method performs a query (returning the output of objects.filter()), but when the objects' values are changed in the database, the results of objects.filter() don't update until I bounce the server. How can I force the query to evaluate each time the method is called?
The details:
At the model level, I've defined a method to return all non-expired Announcement objects:
class AnnouncementManager(models.Manager):
# this is the method
def activeAnnouncements(self, expiry_time):
activeAnnouncements = self.filter(expires_at__gt=expiry_time).all()
return activeAnnouncements
class Announcement(models.Model):
...
expires_at = models.DateTimeField("Expires", null=True)
objects = AnnouncementManager()
I call this from a view with:
activeAnnouncements = Announcement.objects.activeAnnouncements()
However, when an Announcement object's data is updated in the database (e.g. expires_at is changed), the query still reflects the old data until the server is bounced. After reading http://docs.djangoproject.com/en/dev/ref/models/querysets/#when-querysets-are-evaluated, I tried to force the query to reevalute by updating the method as follows:
def activeAnnouncements(self, expiry_time):
# use boolean evaluation to force reevaluation of queryset
if self.filter(expires_at__gt=expires):
pass
activeAnnouncements = self.filter(expires_at__gt=expiry_time).all()
return activeAnnouncements
This had no effect.
Thanks for your help!
Update:
Can you please show the full code of where you are calling it?
This is the view which calls it:
#never_cache
def front_page(request):
'''
Displays the current announcements
'''
announcements = ''
activeAnnouncements = Announcement.objects.activeAnnouncements().order_by('-id')
if not request.user.get_profile().admin:
hide_before = request.user.get_profile().suppress_messages_before
if hide_before is not None:
activeAnnouncements = activeAnnouncements.filter(created_at__gt=hide_before)
if activeAnnouncements.count() > 0:
announcements = activeAnnouncements
else:
announcements = ""
return render_to(
request
, "frontpage.html"
, {
'announcements' : announcements
})
And here's the full version of the Announcement and AnnouncementManager models (excerpted above):
class AnnouncementManager(models.Manager):
# Get all active announcements (i.e. ones that have not yet expired)
def activeAnnouncements(self, expires=datetime.datetime.now()):
activeAnnouncements = self.filter(expires_at__gt=expires).all()
return activeAnnouncements
class Announcement(models.Model):
text = models.TextField()
subject = models.CharField(max_length=100)
expires_at = models.DateTimeField("Expires", null=True)
created_at = models.DateTimeField("Creation Time", auto_now_add=True)
created_by = models.ForeignKey(User, related_name="created_announcements")
updated_at = models.DateTimeField("Update Time", auto_now=True)
updated_by = models.ForeignKey(User, related_name="updated_announcements")
objects = AnnouncementManager()
def __unicode__(self):
return self.subject
Aha. The full version of the Manager method has a big difference from the one you originally posted, and it's there that the trouble is.
def activeAnnouncements(self, expires=datetime.datetime.now()):
This is one of the biggest Python gotchas: default function parameters are evaluated when the function is defined, not when it is called. So the default value for expiry will be set to whenever the server process was first started. Read the effbot's explanation of the problem. (Note it's a Python problem, not anything to do with Django querysets.)
Instead, do this:
def activeAnnouncements(self, expires=None):
if expires is None:
expires = datetime.datetime.now()
activeAnnouncements = self.filter(expires_at__gt=expires).all()
return activeAnnouncements
Is this an answer to your question?