Beispiel #1
0
def confirm_reservation(reservation):
    '''
    Check if reservation can be immediately rented and send appropriate email.
    '''
    preceding_reservations = Reservation.objects.filter(Q_reservation_active|Q(rental__isnull=False, rental__end_date__isnull=True), book_copy=reservation.book_copy, id__lt=reservation.id)
    if not preceding_reservations and reservation.start_date <= utils.today():
        mail.reservation_active(reservation)
        reservation.active_since = utils.today()
        reservation.save()
    else:
        mail.made_reservation(reservation)
Beispiel #2
0
def max_prolongation_period(rental):
    '''
    Desc:
        Returnes number of days -- for how long book can be prolonged.
    Args:
        rental -- instance of Rental.
    '''
    assert isinstance(rental, Rental)
    reservation = rental.reservation

    config = Config()
    days_left_to_enable_prolongation = config.get_int('min_days_left_to_enable_prolongation')
    days_left_to_rend    = (reservation.end_date - utils.today()).days
    enabled_prolongation = days_left_to_rend <= days_left_to_enable_prolongation
    if not enabled_prolongation:
        return 0

    max_period = config.get_int('max_prolongation_days')
    kopy       = reservation.book_copy
    for rsv in kopy.reservation_set.exclude(for_whom=reservation.for_whom):
        if rsv.start_date <= reservation.end_date < rsv.end_date:         # someone else reserved -> prolongation impossible
            return 0
        if reservation.end_date == rsv.start_date:
            return 0
        if rsv.end_date <= reservation.end_date:                          # other rsv finished -> no collision, continue processing
            continue
        if reservation.end_date < rsv.start_date:                         # other rsv starts in future -> try to shrink max_period
            max_period = min(max_period, (rsv.start_date - reservation.end_date).days - 1)
    return max_period
Beispiel #3
0
def mark_available(book_copy):
    '''
    Desc:
        If there is an active reservation awaiting for this copy, let it know.
    '''
    reservations = Reservation.objects.filter(Q_reservation_active, book_copy=book_copy).filter(start_date__lte=utils.today())
    if reservations.count() > 0:  # TODO: this probably can be done more effectively
        first_reservation = reservations[0]
        first_reservation.active_since = utils.today()
        first_reservation.save()
        mail.reservation_active(first_reservation)
Beispiel #4
0
def get_reservations_for_copies(copies_ids, only=[], related=[]):
    '''
    Collects and returns list of Reservation objects for given book copies.

    Args:
        copies_ids -- ids of copies for which you want to get reservations. List of ints.
        only -- model fields one need to use. List of strings. As default 'book_copy__id' is used.
    '''
    only_fields = only + ['book_copy__id']
    reservations = Reservation.objects.only(*only_fields) \
                                      .filter(book_copy__id__in=copies_ids) \
                                      .filter(start_date__lte=utils.today()) \
                                      .filter(Q_reservation_active)
    return reservations
Beispiel #5
0
def reservation_status(reservation):
    '''
    Desc:
        returns reservation status:
            integer means for how many days book can be rented
            string is explaining why rental is not possible
    '''
    all = Reservation.objects.filter(book_copy=reservation.book_copy)\
                             .filter(rental=None)\
                             .filter(when_cancelled=None)\
                             .filter(end_date__gte=utils.today())
    if reservation not in all:
        return 'Incorrect reservation'
    # book rented:
    if Rental.objects.filter(reservation__book_copy=reservation.book_copy).filter(end_date=None).count() > 0:
        return 'Copy rented.'
    # book unavailable:
    if reservation.book_copy.state.is_available == False:
        return 'Copy not available (' + reservation.book_copy.state.name + ').'
    # too early
    if reservation.start_date > utils.today():
        return 'Reservation is not yet active'
    # reservation cannot be purchased for there are some reservation before this one starts
    if reservation.start_date > utils.today() and all.filter(start_date__lt=reservation.start_date).count() > 0:
        return 'There are reservations before this one starts.'
    # some older reservation is active
    if all.filter(id__lt=reservation.id).filter(start_date__lte=utils.today()).filter(Q_reservation_active).count() > 0:
        # WARNING/NOTE: this might need modification if additional conditions are added to definition of active reservation
        return 'Reservation in queue'
    config = Config()
    max_allowed = config.get_int('rental_duration')
    try:
        max_possible = (min([r.start_date for r in all.filter(id__lt=reservation.id).filter(start_date__gt=utils.today())]) - utils.today()).days
    except ValueError:
        max_possible = max_allowed
    return min(max_allowed, max_possible)
Beispiel #6
0
def rent(reservation, librarian):
    '''
    Desc:
        Librarian rents book indicated by reservation. Return value says when it is supposed to be returned
    '''
    if not librarian.has_perm('baseapp.add_rental'):
        raise PermissionDenied
    if not librarian in reservation.book_copy.location.get_all_maintainers():
        raise PermissionDenied('One can only rent from location one maintains')
    if not is_reservation_rentable(reservation):
        raise PermissionDenied('Reservation not rentable.')
    max_end_date = utils.today() + timedelta(reservation_status(reservation)-1) # return one day before it's reserved
    if max_end_date < reservation.end_date:
        reservation.end_date = max_end_date
        reservation.save()
    rental = Rental(reservation=reservation, who_handed_out=librarian, start_date=utils.now())
    rental.save()
    mail.made_rental(rental)
    return reservation.end_date
Beispiel #7
0
def show_reservations(request, shipment_requested=False, only_rentable=True, all_locations=False):
    if request.method == 'POST':
        post = request.POST
        reservation = get_object_or_404(Reservation, id=post['reservation_id'])
        if 'rent' in post:
            user = reservation.for_whom
            until = rent(reservation, request.user)
            messages.info(request, 'Rented %s (%s) to %s until %s.' % \
                (reservation.book_copy.book.title, reservation.book_copy.shelf_mark, user_full_name(reservation.for_whom), until.isoformat()))
        if 'cancel' in post:
            cancel_reservation(reservation, request.user)
            messages.info(request, 'Cancelled.')
    context = {}
    locations = request.user.location_set.all()  # locations maintained by user
    reservations = Reservation.objects.filter(Q_reservation_active)
    if shipment_requested:
        reservations = reservations.filter(shipment_requested=True).select_related()
    if not all_locations:
        reservations = reservations.filter(book_copy__location__in=locations)
    if only_rentable:
        reservations = [r for r in reservations.filter(start_date__lte=utils.today()) if is_reservation_rentable(r)]

    reservation_list = [
                        { 'id'                : r.id,
                          'user'              : user_full_name(r.for_whom.id),
                          'start_date'        : r.start_date,
                          'end_date'          : r.end_date,
                          'location'          : r.book_copy.location,
                          'shelf_mark'        : r.book_copy.shelf_mark,
                          'title'             : r.book_copy.book.title,
                          'authors'           : [a.name for a in r.book_copy.book.author.all()],
                          'rental_possible'   : is_reservation_rentable(r),
                        } for r in reservations ]
    context.update({ 'rows' : reservation_list })
    if shipment_requested:
        context.update({ 'header' : 'Shipment requests',
                         'shipments' : True, })
    elif not only_rentable:
        context.update({ 'header' : 'All active reservations',
                         'show_all' : True, })
    else:
        context.update({ 'header' : 'Current reservations'})
    return render_response(request, 'current_reservations.html', context)
Beispiel #8
0
sys.path.insert(0, imaging_path)
sys.path.insert(0, django_path)
sys.path.insert(0, project_path)
sys.path.insert(0, app_path)

from baseapp.models import *
from django.contrib.auth.models import User
from baseapp.utils import today, after_days
from baseapp.config import Config
from datetime import datetime
import baseapp.views_aux as aux
import baseapp.emails as mail

for r in Rental.objects.all():
    if not r.end_date:
        if r.reservation.end_date < today(
        ):  # reservation expired: notify reader every day
            mail.overdued(r)
        if r.reservation.end_date == after_days(
                Config().get_int('due_remind_time')):
            mail.returnal_date_coming(
                r)  # reservation will expire soon: notify reader

for r in Reservation.objects.filter(aux.Q_reservation_active):
    if not r.active_since and aux.is_reservation_rentable(r):
        # reservation has just become active - notify user
        r.active_since = today()
        r.save()
        mail.reservation_active(r)

    if r.active_since and r.active_since <= after_days(
            -1 * Config().get_int('reservation_rush')):
Beispiel #9
0
sys.path.insert(0, project_path)
sys.path.insert(0, app_path)


from baseapp.models import *
from django.contrib.auth.models import User
from baseapp.utils import today, after_days
from baseapp.config import Config
from datetime import datetime
import baseapp.views_aux as aux
import baseapp.emails as mail


for r in Rental.objects.all():
    if not r.end_date :
        if r.reservation.end_date < today():  # reservation expired: notify reader every day
            mail.overdued(r)
        if r.reservation.end_date == after_days(Config().get_int('due_remind_time')):
            mail.returnal_date_coming(r)      # reservation will expire soon: notify reader

for r in Reservation.objects.filter(aux.Q_reservation_active):
    if not r.active_since and aux.is_reservation_rentable(r):
        # reservation has just become active - notify user
        r.active_since = today()
        r.save()
        mail.reservation_active(r)
    
    if r.active_since and r.active_since <= after_days(-1*Config().get_int('reservation_rush')):
        # reservation has been active for 'reservation_rush' days, so it expires
        r.when_cancelled = datetime.now()
        r.save()
Beispiel #10
0
def get_time_bar_code_for_copy(config, book_copy, from_date, to_date):
    """
    Returns time bar's html for given copy and date range.
    
    Args:
        book_copy -- instance of BookCopy
        config -- instance of Config
        from_date, to_date -- instances of datetime.date . Range from which records will be displayed.
        
    Returns:
        If from_date > to_date, then empty string is returned.
    """
    assert isinstance(book_copy, BookCopy)
    assert isinstance(from_date, date)
    assert isinstance(to_date, date)

    date_range = (from_date, to_date)
    if from_date > to_date:
        return ''

    Q_start_inside_range = Q(start_date__gte=from_date) & Q(
        start_date__lte=to_date)  #   >   |---<--------|
    Q_end_inside_range = Q(end_date__gte=from_date) & Q(
        end_date__lte=to_date)  #       |--------->--|  <
    Q_covers_whole_range = Q(start_date__lte=from_date) & Q(
        end_date__gte=to_date)  #   |---->----<------|

    reservations = Reservation.objects.filter(book_copy=book_copy) \
                                      .filter(when_cancelled=None) \
                                      .filter(Q_start_inside_range | Q_end_inside_range | Q_covers_whole_range) \
                                      .order_by('start_date')
    # .filter(Q(rental=None)|Q(rental__end_date=None)) \

    segments = []
    for r in reservations:
        seg = Segment(r.start_date, r.end_date)
        seg.who_reserved = r.for_whom
        rentals = list(r.rental_set.all())
        seg.rental = rentals[0] if rentals else None
        seg.overdued = False
        if seg.rental:
            end = seg.rental.end_date.date(
            ) if seg.rental.end_date else seg.rental.reservation.end_date if r.end_date > today(
            ) else today()
            seg.overdued = r.end_date < end
            seg.overdue_time = end - r.end_date
            seg.end = end
        segments.append(seg)
    # NOTE: if one would like to add some extra informations to segments, it can be done above.
    #       This is why segment is an object and not a tuple.

    # ok, we gathered segments, now let's create time bar on top of them
    tb = TimeBar(config, one=timedelta(1))
    result_segments = tb.divide_colliding_segments(
        segments, shuffle=False)  # detect collisions
    grouped_segs = tb.split_segments_by_depth(
        result_segments)  # and collect them in groups

    # set number of colliding groups. Cut surplus.
    max_colliding_groups = 10
    grouped_segs = grouped_segs[:max_colliding_groups]

    # "correct" each group, which means add "green segments" in between, cut/enlarge start and end dates properly
    result_segments = []
    for group in grouped_segs:
        # each of operations below may drop some segments. It may happen that all segments will be dropped.
        filled_group = tb.add_segment_between_each_two(group)
        if filled_group:
            filled_group = tb.start_with_value(filled_group, date_range[0])
        if filled_group:
            filled_group = tb.end_with_value(filled_group, date_range[1])
        if filled_group:
            result_segments.append(filled_group)

    # if there is no reservations, then add available one-Segment group
    if not result_segments:
        group = [Segment(from_date, to_date, is_available=True)]
        result_segments.append(group)

    # create group which is a flattened version of time bar
    if len(result_segments) > 1:
        flattened_group = tb.get_flattened_group(result_segments)
        filled_group = tb.add_segment_between_each_two(flattened_group)
        if filled_group:
            filled_group = tb.start_with_value(filled_group, date_range[0])
        if filled_group:
            filled_group = tb.end_with_value(filled_group, date_range[1])
        flattened_group = filled_group
        for segment in flattened_group:
            segment.from_flattened_group = True
        result_segments.insert(0, flattened_group)

    # generate html
    html = tb.get_html_for_segments(result_segments)

    # add scale (or two scales if there is a lot of colliding segments)
    scale = tb.get_html_of_scale(result_segments) if result_segments else ''
    if len(result_segments) > 100000:
        html = scale + html + scale
    elif len(result_segments) > 0:
        html = scale + html
    return html
Beispiel #11
0
def get_report_data(report_type, from_date, to_date, order_by=[]):
    """
    Desc:
        Returns data for creating selected report.

    Args:
        report_type - basestring. type of the report
        from_date, to_date - basestring. describe the time period for the report.
        order_by - iterable. Names of parameters by which result should be ordered by.
                   Currently only one item is supported, bo using order_by will more 
                   then one element will take no effect. 

    Return:
        dict { 'report'   : report data
               'template' : template to be rendered (only filename. Proper dir will be attached elsewhere)
               'error'    : error message if sth goes wrong
               'ordering' : list of names used to sort report data
             }
    """
    assert from_date
    assert to_date
    order_by = list(order_by)
    order_by = order_by[:1] if order_by else []         # only one name can be used to order data 
    
    # NOTE: `ob` stands from order by
    ob_rename_dict = {'title'        : order_asc_by_key('title'),
                      '-title'       : order_desc_by_key('title'),
                      'location'     : order_asc_by_key('location_str'),
                      '-location'    : order_desc_by_key('location_str'),
                      'shelf_mark'   : order_asc_by_key('shelf_mark'),
                      '-shelf_mark'  : order_desc_by_key('shelf_mark'),
                      'status'       : order_asc_by_key('status_str'),
                      '-status'      : order_desc_by_key('status_str'),
                      'for_whom'     : order_asc_by_key('for_whom'),
                      '-for_whom'    : order_desc_by_key('for_whom'),
                      'when'         : order_asc_by_key('when'),
                      '-when'        : order_desc_by_key('when'),
                      'librarian'    : order_asc_by_key('by_whom'),
                      '-librarian'   : order_desc_by_key('by_whom'),
                      'name'         : order_asc_by_key('name'),
                      '-name'        : order_desc_by_key('name'),
                      'num_cancels'  : order_asc_by_key('num_of_cancels'),
                      '-num_cancels' : order_desc_by_key('num_of_cancels'),
                      'num_overdues' : order_asc_by_key('num_of_overdues'),
                      '-num_overdues': order_desc_by_key('num_of_overdues'),
                      'len_overdues' : order_asc_by_key('duration_of_overdues'),
                      '-len_overdues': order_desc_by_key('duration_of_overdues'),
                      'num_rentals'  : order_asc_by_key('num_of_rentals'),
                      '-num_rentals' : order_desc_by_key('num_of_rentals'),
                      'num_rsvs'     : order_asc_by_key('num_of_reservations'),
                      '-num_rsvs'   : order_desc_by_key('num_of_reservations'),
                      }

    ob_in_status         = ['title', '-title', 'location', '-location', 'shelf_mark', '-shelf_mark', 
                            'status', '-status', 'for_whom', '-for_whom', 'when', '-when', 'librarian', '-librarian']
    ob_in_often_rented   = ['title', '-title', 'num_rentals', '-num_rentals']
    ob_in_often_reserved = ['title', '-title', 'num_rsvs', '-num_rsvs']
    ob_in_black_list     = ['name', '-name', 'num_cancels', '-num_cancels', 'num_overdues', '-num_overdues', 'len_overdues', '-len_overdues']
    ob_in_lost_books     = copy(ob_in_status)
    
    if report_type == u'status':
        book_infos = []
        copies = BookCopy.objects.select_related('id', 'state', 'book__title', 'location', 'location__building', 'shelf_mark')
        last_rentals = {}
        from datetime import timedelta, datetime
        status_day = utils.str_to_date(from_date, utils.today())
        status_day_start = datetime(status_day.year, status_day.month, status_day.day, 0, 0, 0)
        status_day_end = datetime(status_day.year, status_day.month, status_day.day, 23, 59, 59)
        
#         Q_start_date_filter =  Q(start_date__contains='')      # Q_all
#         if from_date:
#             Q_start_date_filter = Q(start_date__gte=from_date)
#         Q_end_date_filter = Q(end_date__isnull=True)
#         if to_date:
#             Q_end_date_filter = Q(end_date__lte=to_date) | Q(end_date__isnull=True)
#         rentals = Rental.objects.select_related('reservation', 'reservation__book_copy__id') \
#                                 .filter(Q_start_date_filter) \
#                                 .filter(Q_end_date_filter)
        Q_start_date_filter = Q(start_date__lte=status_day_end)
        Q_end_date_filter = (Q(end_date__isnull=True) | Q(end_date__gte=status_day_start))
        rentals = Rental.objects.select_related('reservation', 'reservation__book_copy__id') \
                                .filter(Q_start_date_filter) \
                                .filter(Q_end_date_filter)

        for rental in rentals:
            book_copy_id = rental.reservation.book_copy.id
            start_date = rental.start_date
            if book_copy_id not in last_rentals:
                last_rentals.update({book_copy_id: {'when'    : start_date,
                                                    'for_whom': rental.reservation.for_whom,
                                                    'by_whom' : rental.who_handed_out,
                                                                }})
            elif last_rentals[book_copy_id]['when'] < start_date:
                last_rentals.update({book_copy_id: {'when'    : start_date,
                                                    'for_whom': rental.reservation.for_whom,
                                                    'by_whom' : rental.who_handed_out,
                                                                }})

        book_infos = []
        statuses = book_copies_status(copies)
        for kopy in copies:
            copy_status = statuses[kopy.id]
            not_rented_yet = copy_status['copy'].id not in last_rentals
            if not_rented_yet:
                for_whom = when = by_whom = '- never -'
            else:
                last_rental = last_rentals[copy_status['copy'].id]
                (for_whom, when, by_whom) = (last_rental['for_whom'], last_rental['when'].date(), last_rental['by_whom'])            
            
            book_infos.append({ 'title'         : copy_status['copy'].book.title,
                                'shelf_mark'    : copy_status['copy'].shelf_mark,
                                'copy'          : kopy,
                                'location'      : copy_status['copy'].location,
                                'location_str'  : unicode(copy_status['copy'].location).lower(),     # field for ordering purpose
                                'status'        : copy_status['status'],
                                'status_str'    : unicode(copy_status['status']).lower(),
                                'for_whom'      : for_whom,
                                'when'          : unicode(when),
                                'by_whom'       : by_whom,
                                'not_rented_yet': not_rented_yet})
        
        sort_by = list(set(order_by) & set(ob_in_status))
        if sort_by:
            book_infos.sort(cmp=ob_rename_dict[sort_by[0]])
        return {'report'   : book_infos, 
                'template' : 'library_status.html', 
                'error'    : False,
                'ordering' : sort_by,
                }

    ###

    elif report_type == u'most_often_rented':
        book_infos = []
        books = Book.objects.all()
        rentals = Rental.objects.all()
        date_empty = False
        nums_of_rentals = {}

        if from_date == u'' or to_date == u'':
            date_empty = True
        else:
            try:
                [y, m, d] = map(int,from_date.split('-'))
                start_date = date(y, m, d)
                [y, m, d] = map(int,to_date.split('-'))
                end_date = date(y, m, d)
            except:
                return {'error': True, 'report': [], 'template': 'reports.html'}

        for book in books:
            if book.id not in nums_of_rentals:
                nums_of_rentals.update({book.id: 0})

        for rental in rentals:
            book_id = rental.reservation.book_copy.book.id
            if date_empty:
                nums_of_rentals[book_id] += 1
            else:
                if rental.start_date.date() >= start_date and rental.start_date.date() <= end_date:
                    nums_of_rentals[book_id] += 1

        for book in books:
            book_infos.append({'title': book.title, 'num_of_rentals': nums_of_rentals[book.id], 'id' : book.id, })

        sort_by = list(set(order_by) & set(ob_in_often_rented))
        if sort_by:
            book_infos.sort(cmp=ob_rename_dict[sort_by[0]])
        return {'report'   : book_infos, 
                'template' : 'most_often_rented.html', 
                'error'    : False,
                'ordering' : sort_by,
                }

    ###

    elif report_type == u'most_often_reserved':
        book_infos = []
        books = Book.objects.all()
        reservations = Reservation.objects.all()
        nums_of_reservations = {}
        date_empty = False

        if from_date == u'' or to_date == u'':
            date_empty = True
        else:
            try:
                [y, m, d] = map(int,from_date.split('-'))
                start_date = date(y, m, d)
                [y, m, d] = map(int,to_date.split('-'))
                end_date = date(y, m, d)
            except:
                return {'error': True, 'report': [], 'template': 'reports.html'}

        for book in books:
            if book.id not in nums_of_reservations:
                nums_of_reservations.update({book.id: 0})

        for reservation in reservations:
            book_id = reservation.book_copy.book.id
            if date_empty:
                nums_of_reservations[book_id] += 1
            else:
                if reservation.start_date >= start_date and reservation.start_date <= end_date:
                    nums_of_reservations[book_id] += 1

        for book in books:
            book_infos.append({'title': book.title, 'num_of_reservations': nums_of_reservations[book.id], 'id' : book.id, })

        sort_by = list(set(order_by) & set(ob_in_often_reserved))
        if sort_by:
            book_infos.sort(cmp=ob_rename_dict[sort_by[0]])
        return {'report'   : book_infos, 
                'template' : 'most_often_reserved.html', 
                'error'    : False,
                'ordering' : sort_by,
                }

    ###

    elif report_type == u'black_list':
        nums_of_cancels = {}
        nums_of_overdues = {}
        duration_of_overdues = {}
        user_infos = []
        users = User.objects.all()
        reservations = Reservation.objects.filter(when_cancelled__isnull=False)
        rentals = [ r for r in Rental.objects.all() if r.end_date and r.reservation.end_date < r.end_date.date() ]
        date_empty = False

        if from_date == u'' or to_date == u'':
            date_empty = True
        else:
            try:
                [y, m, d] = map(int,from_date.split('-'))
                start_date = date(y, m, d)
                [y, m, d] = map(int,to_date.split('-'))
                end_date = date(y, m, d)
            except:
                return {'error': True, 'report': [], 'template': 'reports.html'}

        for user in users:
            nums_of_cancels.update({user.id: 0})
            nums_of_overdues.update({user.id: 0})
            duration_of_overdues.update({user.id: 0})

        for rental in rentals:
            user_id = rental.reservation.for_whom.id
            if date_empty:
                nums_of_overdues[user_id] += 1
                duration_of_overdues[user_id] += (rental.end_date.date() - rental.reservation.end_date).days
            else:
                if rental.reservation.start_date >= start_date and rental.reservation.start_date <= end_date:
                    nums_of_overdues[user_id] += 1
                    duration_of_overdues[user_id] += (rental.end_date.date() - rental.reservation.end_date).days

        for reservation in reservations:
            user_id = reservation.for_whom.id
            if date_empty:
                nums_of_cancels[user_id] += 1
            else:
                if reservation.start_date >= start_date and reservation.start_date <= end_date:
                    nums_of_cancels[user_id] += 1

        for user in users:
            user_infos.append({'name': user.last_name + u', ' + user.first_name,
                               'id'  : user.id,
                               'num_of_cancels': nums_of_cancels[user.id],
                               'num_of_overdues' : nums_of_overdues[user.id],
                               'duration_of_overdues' : duration_of_overdues[user.id],
                               })

        sort_by = list(set(order_by) & set(ob_in_black_list))
        if sort_by:
            user_infos.sort(cmp=ob_rename_dict[sort_by[0]])

        return {'report'   : user_infos, 
                'template' : 'black_list.html', 
                'error'    : False,
                'ordering' : sort_by,
                }

    ###

    elif report_type == u'lost_books':
        reported_data = get_report_data(u'status', from_date, to_date)
        book_infos = reported_data['report']
        lost_books = filter(lambda b: not b['status'].is_available(), book_infos)
        sort_by = list(set(order_by) & set(ob_in_lost_books))
        if sort_by:
            lost_books.sort(cmp=ob_rename_dict[sort_by[0]])
        return {'report'   : lost_books, 
                'template' : 'unavailable_status.html', 
                'error'    : False,
                'ordering' : sort_by,
                }
    else:
        return {'report': [], 'template': 'reports.html', 'error': True}
Beispiel #12
0
def get_time_bar_code_for_copy(config, book_copy, from_date, to_date):
    """
    Returns time bar's html for given copy and date range.
    
    Args:
        book_copy -- instance of BookCopy
        config -- instance of Config
        from_date, to_date -- instances of datetime.date . Range from which records will be displayed.
        
    Returns:
        If from_date > to_date, then empty string is returned.
    """
    assert isinstance(book_copy, BookCopy)
    assert isinstance(from_date, date)
    assert isinstance(to_date, date)

    date_range = (from_date, to_date)
    if from_date > to_date:
        return ''
    
    Q_start_inside_range = Q(start_date__gte=from_date) & Q(start_date__lte=to_date)  #   >   |---<--------|
    Q_end_inside_range   = Q(end_date__gte=from_date) & Q(end_date__lte=to_date)      #       |--------->--|  <
    Q_covers_whole_range = Q(start_date__lte=from_date) & Q(end_date__gte=to_date)    #   |---->----<------|
    
    reservations = Reservation.objects.filter(book_copy=book_copy) \
                                      .filter(when_cancelled=None) \
                                      .filter(Q_start_inside_range | Q_end_inside_range | Q_covers_whole_range) \
                                      .order_by('start_date')
                                      # .filter(Q(rental=None)|Q(rental__end_date=None)) \

    segments = []
    for r in reservations:
        seg = Segment(r.start_date, r.end_date)
        seg.who_reserved = r.for_whom
        rentals = list(r.rental_set.all())
        seg.rental = rentals[0] if rentals else None
        seg.overdued = False
        if seg.rental:
            end = seg.rental.end_date.date() if seg.rental.end_date else seg.rental.reservation.end_date if r.end_date > today() else today()
            seg.overdued = r.end_date < end
            seg.overdue_time = end - r.end_date
            seg.end = end
        segments.append(seg)
    # NOTE: if one would like to add some extra informations to segments, it can be done above.
    #       This is why segment is an object and not a tuple.

    # ok, we gathered segments, now let's create time bar on top of them
    tb = TimeBar(config, one=timedelta(1))
    result_segments = tb.divide_colliding_segments(segments, shuffle=False)   # detect collisions
    grouped_segs = tb.split_segments_by_depth(result_segments)                # and collect them in groups
    
    # set number of colliding groups. Cut surplus. 
    max_colliding_groups = 10
    grouped_segs = grouped_segs[:max_colliding_groups]
    
    # "correct" each group, which means add "green segments" in between, cut/enlarge start and end dates properly
    result_segments = []
    for group in grouped_segs:
        # each of operations below may drop some segments. It may happen that all segments will be dropped.
        filled_group = tb.add_segment_between_each_two(group)
        if filled_group:
            filled_group = tb.start_with_value(filled_group, date_range[0])
        if filled_group:
            filled_group = tb.end_with_value(filled_group, date_range[1])
        if filled_group:
            result_segments.append(filled_group)
    
    # if there is no reservations, then add available one-Segment group
    if not result_segments:
        group = [Segment(from_date, to_date, is_available=True)]
        result_segments.append(group)

    # create group which is a flattened version of time bar
    if len(result_segments) > 1:
        flattened_group = tb.get_flattened_group(result_segments)
        filled_group = tb.add_segment_between_each_two(flattened_group)
        if filled_group:
            filled_group = tb.start_with_value(filled_group, date_range[0])
        if filled_group:
            filled_group = tb.end_with_value(filled_group, date_range[1])
        flattened_group = filled_group
        for segment in flattened_group:
            segment.from_flattened_group = True
        result_segments.insert(0, flattened_group)

    # generate html
    html = tb.get_html_for_segments(result_segments)

    # add scale (or two scales if there is a lot of colliding segments)
    scale = tb.get_html_of_scale(result_segments) if result_segments else ''
    if len(result_segments) > 100000:
        html = scale + html + scale
    elif len(result_segments) > 0:
        html = scale + html
    return html
Beispiel #13
0
def show_user_rentals(request, user_id=False):
    '''
    Desc:
        Shows user's current rentals. Allows returning books.
    '''
    if not user_id:
        user = request.user
    else:
        try:
            user = User.objects.get(id=user_id)
        except User.DoesNotExist:
            return render_not_found(request, item_name='User')

    post = request.POST  # a shorthand

    context = { 'first_name' : user.first_name,
                'last_name' : user.last_name,
                'email' : user.email,
                'reader' : user,
                }

    # Return button clicked for a rental:
    # but we care about it only if he is supposed to
    if request.user.has_perm('baseapp.change_rental') and\
       request.method == 'POST' and\
       'returned' in post:
        try:
            until_when = return_rental(librarian=request.user, rental_id=post['returned'])
            context['message'] = 'Successfully returned'
            messages.info(request, 'Successfully returned')
        except Rental.DoesNotExist:
            return render_not_found(request, item_name='Rental')
        except PermissionDenied:  # this might happen if user doesn't have 'change_rental' permission
            return render_forbidden

    # logger for prolongation
    prol_logger = utils.get_logger('prolong')

    # sending prolongation request to librarians
    if request.method == 'POST' and 'prolong_request' in post:
        rental_id = int(post['prolong_rental_id'])
        prol_logger.info("request ;; user %d %s ;; rental_id %d", user.id, user.get_full_name(), rental_id)
        send_prolongation_request(request, rental_id, user)

    # prolonging rental
    if request.method == 'POST' and 'prolong' in post and request.user.has_perm('baseapp.change_rental'):
        rental_id   = int(post['prolong_rental_id'])
        requester   = user
        prolongator = request.user
        prol_logger.info("prolonging ;; prolongator %d %s ;; requester %d %s ;; rental_id %d",
                         prolongator.id, prolongator.get_full_name(),
                         requester.id, requester.get_full_name(),
                         rental_id)
        prolong_rental(request, rental_id, requester, prolongator)

    # find user's rentals
    user_rentals = Rental.objects.filter(reservation__for_whom=user.id).filter(end_date__isnull=True)

    # and put them in a dict:
    days_left_to_enable_prolongation = Config().get_int('min_days_left_to_enable_prolongation')
    rent_list = []
    for r in user_rentals:
        max_period               = max_prolongation_period(r)
        days_left_to_rend        = (r.reservation.end_date - utils.today()).days
        enabled_prolongation     = days_left_to_rend <= days_left_to_enable_prolongation
        can_request_prolongation = enabled_prolongation and (request.user==r.reservation.for_whom)           # owner of reservation
        can_prolong              = enabled_prolongation \
                                    and max_period > 0 \
                                    and request.user.has_perm('baseapp.change_rental') \
                                    and (request.user in r.reservation.book_copy.location.get_all_maintainers())

        # msg('enabled_prolongation: %d' % enabled_prolongation)
        # msg('can_request_prolongation: %d' % can_request_prolongation)
        # msg('can_prolong: %d' % can_prolong)
        # msg('days_left_to_enable_prolongation: %d' % days_left_to_enable_prolongation)
        # msg('max_period: %d' % max_period)

        kopy = r.reservation.book_copy
        book = kopy.book
        rent_list.append({
                'id'                         : r.id,
                'shelf_mark'                 : kopy.shelf_mark,
                'title'                      : book.title,
                'kopy'                       : kopy,
                'book'                       : book,
                'authors'                    : list(sorted([a.name for a in book.author.all()])),
                'from_date'                  : r.start_date.date(),
                'to_date'                    : r.reservation.end_date,
                'returnable'                 : request.user in kopy.location.get_all_maintainers(),
                'rental'                     : r,
                'can_request_prolongation'   : can_request_prolongation,
                'can_prolong'                : can_prolong,
                'prolongation_length'        : max_period,
                'cannot_request_nor_prolong' : not (can_request_prolongation or can_prolong),
                })

    # put rentals into context
    context['rows'] = rent_list

    # if user can change rentals then he can return books
    template = 'user_rentals_return_allowed.html' if request.user.has_perm('baseapp.change_rental') else 'user_rentals.html'

    return render_response(request, template, context)
Beispiel #14
0
def prolong_rental(request, rental_id, requester, prolongator):
    '''
    Desc:
        Prolongs rental if possible.
    Args:
        request
        rental_id
        requester -- User. Owner of reservation and rental (one who is in possesion of book)
        prolongator
    '''
    prol_logger = utils.get_logger('prolong')
    prol_logger.info("prolong_rental()....")

    assert isinstance(rental_id, int)
    assert isinstance(requester, User)
    assert isinstance(prolongator, User)
    r = None
    try:
        r = Rental.objects.get(pk=rental_id, reservation__for_whom=requester)
    except Rental.DoesNotExist:
        messages.error(request, "Rental wasn't found, unable to prolong.")
        return

    prol_logger.info("prolong_rental() preconditions valid, rental present.")

    days_left_to_enable_prolongation = Config().get_int('min_days_left_to_enable_prolongation')
    days_left_to_rend    = (r.reservation.end_date - utils.today()).days
    enabled_prolongation = days_left_to_rend <= days_left_to_enable_prolongation
    max_period           = max_prolongation_period(r)
    can_prolong          = enabled_prolongation and max_period > 0 and prolongator.has_perm('baseapp.change_rental')

    prol_logger.info("prolong_rental() "
                     + ";; rental_id %d "
                     + ";; days_left_to_enable_prolongation %d "
                     + ";; days_left_to_rend %d "
                     + ";; enabled_prolongation %d "
                     + ";; max_period %d "
                     + ";; can_prolong %d "
                     %
                     r.id,
                     days_left_to_enable_prolongation,
                     days_left_to_rend,
                     enabled_prolongation,
                     max_period,
                     can_prolong
                     )

    if prolongator not in r.reservation.book_copy.location.get_all_maintainers():
        prol_logger.error("prolong_rental() prolongator not a location maintainer.")
        messages.error(request, "You cannot prolong this book - it's not from location you maintain.")
        return
    if not enabled_prolongation:
        prol_logger.error("prolong_rental() prolongator not enabled.")
        messages.error(request, "It is TOO EARLY to prolong this rental: please wait for %d days." % (days_left_to_rend - days_left_to_enable_prolongation))
        return
    if max_period <= 0:
        prol_logger.error("prolong_rental() max_period less then zero.")
        messages.error(request, "Rental cannot be prolonged - probably someone reserved this book.")
        return
    if can_prolong:
        import datetime as dt
        old_end_date = r.reservation.end_date
        new_end_date = r.reservation.end_date + dt.timedelta(max_period)
        r.reservation.end_date = new_end_date
        r.reservation.save()
        prol_logger.info("prolong_rental() prolonged ;; rental_id %d "
                         + ";; old_end_date %s "
                         + ";; new_end_date %s "
                         %
                         r.id,
                         str(old_end_date),
                         str(new_end_date)
                         )

        mail.prolongated(r, prolongator, extra={'old_end_date': old_end_date,
                                                'new_end_date': new_end_date,
                                                    })
        messages.info(request, "Prolonged for %d days." % max_period)
    else:
        prol_logger.error("prolong_rental() you cannot prolong the rental.")
        messages.error(request, "You cannot prolong the rental.")
Beispiel #15
0
def book_copies_status(copies):
    '''
    Desc:
        Returns statuses of given copies.

    Args:
        copies -- list of BookCopy instances

    Returns:
        dict like { copy_id : {'copy' : given_copy_instance,
                               'status' : BookCopyStatus instance
                              },
                    ...
                  }
    '''
    copies_ids = [ c.id for c in copies ]

    result = {}
    for kopy in copies:
        result[kopy.id] = {}
        result[kopy.id]['copy'] = kopy
        result[kopy.id]['status'] = None
        if not kopy.state.is_available:
            result[kopy.id]['status'] = BookCopyStatus(available=False, explanation=u'Unavailable: ' + kopy.state.name)

    # find rented copies
    rentals = get_active_rentals_for_copies(copies_ids)
    for rental in rentals:
        copy_id = rental.reservation.book_copy.id
        if not result[copy_id]['status']:
            user = rental.reservation.for_whom
            explanation = u'Rented until {0} by {1}'.format(rental.reservation.end_date.isoformat(),
                                                            u'<a href="mailto:{0}">{0}</a>'.format(user.email))
            result[copy_id]['status'] = BookCopyStatus(available=False, explanation=explanation)

    # find reserved copies, that can already be rented
    reservations = get_reservations_for_copies(copies_ids)
    for reservation in reservations:
        copy_id = reservation.book_copy.id
        if not result[copy_id]['status']:
            result[copy_id]['status'] = BookCopyStatus(available=False, explanation=u'Reserved')

    # finds first (in sense of timeline) reservation for each copy.
    rsvs = Reservation.objects.filter(book_copy__id__in=copies_ids)\
                              .filter(Q_reservation_active)\
                              .values('book_copy__id')\
                              .annotate(min_start_date=Min('start_date'))

    # update statuses for copies unstatused in code above -- copies are rentable, but we don't know for how long, yet.
    # rsvs contains information about nearest (but in future) reservation.
    config = Config()
    max_allowed = config.get_int('rental_duration')
    for rsv in rsvs:
        copy = rsv['book_copy__id']
        if result[copy]['status']:
            continue
        try:
            min_start_date = rsv['min_start_date']
            max_possible = (min_start_date - utils.today()).days - 1
            if max_possible < 0:
                raise EntelibError('book_copy_status error: copy reserved') # this should not happen since all active reservations should already have status
        except ValueError:
            max_possible = max_allowed
        result[copy]['status'] = BookCopyStatus(available=True, nr_of_days=min(max_allowed, max_possible))

    for key in result.keys():
        if not result[key]['status']:
            result[key]['status'] = BookCopyStatus(available=True, nr_of_days=max_allowed)

    return result
Beispiel #16
0

def is_reservation_rentable(reservation):
    '''
    Desc:
        Returns True if a reserved book can be rented; False otherwise
    '''
    status = reservation_status(reservation)
    if isinstance(status, int) and status >= 0:
        return True
    else:
        return False


# Q object filtering Reservation objects to be only active (which means not rented or cancelled or expired)
Q_reservation_active = Q(when_cancelled=None) & Q(rental=None) & Q(end_date__gte=utils.today())


def reservation_status(reservation):
    '''
    Desc:
        returns reservation status:
            integer means for how many days book can be rented
            string is explaining why rental is not possible
    '''
    all = Reservation.objects.filter(book_copy=reservation.book_copy)\
                             .filter(rental=None)\
                             .filter(when_cancelled=None)\
                             .filter(end_date__gte=utils.today())
    if reservation not in all:
        return 'Incorrect reservation'