<commit_before># -*- encoding: utf-8 -*-
"""
django-thumbs by Antonio Melé
http://django.es
"""
from django.db.models import ImageField
from django.db.models.fields.files import ImageFieldFile
from PIL import Image
from django.core.files.base import ContentFile
import cStringIO

def generate_thumb(img, thumb_size, format):
    """
    Generates a thumbnail image and returns a ContentFile object
    with the thumbnail

    Parameters:
    ===========
    img         File object

    thumb_size  desired thumbnail size, ie: (200,120)

    format      format of the original image ('jpeg','gif','png',...)
                (will also be used for the generated thumbnail)
    """

    img.seek(0)  # see http://code.djangoproject.com/ticket/8222 for details
    image = Image.open(img)

    # Convert to RGB if necessary
    if image.mode not in ('L', 'RGB', 'RGBA'):
        image = image.convert('RGB')

    # get size
    thumb_w, thumb_h = thumb_size

    # not quad
    # make a thumbnail then expand the canvas to the full desired size
    # centering as we go
    image2 = image
    image2.thumbnail(thumb_size, Image.ANTIALIAS)
    oldX, oldY = image2.size
    mode = "RGB"
    image2 = Image.new(mode, thumb_size, "white")
    if oldX < thumb_w:
        x1, y1 = ((thumb_w - oldX) / 2, 0)
    else:
        x1, y1 = (0, (thumb_h-oldY)/2)

    image2.paste(image, (x1, y1))

    io = cStringIO.StringIO()
    # PNG and GIF are the same, JPG is JPEG
    if format.upper()=='JPG':
        format = 'JPEG'

    image2.save(io, format)
    return ContentFile(io.getvalue())

class ImageWithThumbsFieldFile(ImageFieldFile):
    """
    See ImageWithThumbsField for usage example
    """
    def __init__(self, *args, **kwargs):
        super(ImageWithThumbsFieldFile, self).__init__(*args, **kwargs)

        if self.field.sizes:
            def get_size(self, size):
                if not self:
                    return ''
                else:
                    split = self.url.rsplit('.',1)
                    thumb_url = '%s.%sx%s.%s' % (split[0],w,h,split[1])
                    return thumb_url

            for size in self.field.sizes:
                (w,h) = size
                setattr(self, 'url_%sx%s' % (w,h), get_size(self, size))

    def save(self, name, content, save=True):
        super(ImageWithThumbsFieldFile, self).save(name, content, save)

        if self.field.sizes:
            for size in self.field.sizes:
                (w,h) = size
                split = self.name.rsplit('.',1)
                thumb_name = '%s.%sx%s.%s' % (split[0],w,h,split[1])

                # you can use another thumbnailing function if you like
                thumb_content = generate_thumb(content, size, split[1])

                thumb_name_ = self.storage.save(thumb_name, thumb_content)

                if not thumb_name == thumb_name_:
                    raise ValueError('There is already a file named %s' % thumb_name)

    def delete(self, save=True):
        name=self.name
        super(ImageWithThumbsFieldFile, self).delete(save)
        if self.field.sizes:
            for size in self.field.sizes:
                (w,h) = size
                split = name.rsplit('.',1)
                thumb_name = '%s.%sx%s.%s' % (split[0],w,h,split[1])
                try:
                    self.storage.delete(thumb_name)
                except:
                    pass

class ImageWithThumbsField(ImageField):
    attr_class = ImageWithThumbsFieldFile
    """
    Usage example:
    ==============
    photo = ImageWithThumbsField(upload_to='images', sizes=((125,125),(300,200),)

    To retrieve image URL, exactly the same way as with ImageField:
        my_object.photo.url
    To retrieve thumbnails URL's just add the size to it:
        my_object.photo.url_125x125
        my_object.photo.url_300x200

    Note: The 'sizes' attribute is not required. If you don't provide it,
    ImageWithThumbsField will act as a normal ImageField

    How it works:
    =============
    For each size in the 'sizes' atribute of the field it generates a
    thumbnail with that size and stores it following this format:

    available_filename.[width]x[height].extension

    Where 'available_filename' is the available filename returned by the storage
    backend for saving the original file.

    Following the usage example above: For storing a file called "photo.jpg" it saves:
    photo.jpg          (original file)
    photo.125x125.jpg  (first thumbnail)
    photo.300x200.jpg  (second thumbnail)

    With the default storage backend if photo.jpg already exists it will use these filenames:
    photo_.jpg
    photo_.125x125.jpg
    photo_.300x200.jpg

    Note: django-thumbs assumes that if filename "any_filename.jpg" is available
    filenames with this format "any_filename.[widht]x[height].jpg" will be available, too.

    To do:
    ======
    Add method to regenerate thubmnails

    """
    def __init__(self, verbose_name=None, name=None, width_field=None, height_field=None, sizes=None, **kwargs):
        self.verbose_name=verbose_name
        self.name=name
        self.width_field=width_field
        self.height_field=height_field
        self.sizes = sizes
        super(ImageField, self).__init__(**kwargs)
<commit_msg>pep8 fixes<commit_after># -*- encoding: utf-8 -*-
"""
django-thumbs by Antonio Melé
http://django.es
"""
from django.db.models import ImageField
from django.db.models.fields.files import ImageFieldFile
from PIL import Image
from django.core.files.base import ContentFile
import cStringIO

def generate_thumb(img, thumb_size, format):
    """
    Generates a thumbnail image and returns a ContentFile object
    with the thumbnail

    Parameters:
    ===========
    img         File object

    thumb_size  desired thumbnail size, ie: (200,120)

    format      format of the original image ('jpeg','gif','png',...)
                (will also be used for the generated thumbnail)
    """

    img.seek(0)  # see http://code.djangoproject.com/ticket/8222 for details
    image = Image.open(img)

    # Convert to RGB if necessary
    if image.mode not in ('L', 'RGB', 'RGBA'):
        image = image.convert('RGB')

    # get size
    thumb_w, thumb_h = thumb_size

    # not quad
    # make a thumbnail then expand the canvas to the full desired size
    # centering as we go
    image2 = image
    image2.thumbnail(thumb_size, Image.ANTIALIAS)
    oldX, oldY = image2.size
    mode = "RGB"
    image2 = Image.new(mode, thumb_size, "white")
    if oldX < thumb_w:
        x1, y1 = ((thumb_w - oldX) / 2, 0)
    else:
        x1, y1 = (0, (thumb_h - oldY) / 2)

    image2.paste(image, (x1, y1))

    io = cStringIO.StringIO()
    # PNG and GIF are the same, JPG is JPEG
    if format.upper() == 'JPG':
        format = 'JPEG'

    image2.save(io, format)
    return ContentFile(io.getvalue())

class ImageWithThumbsFieldFile(ImageFieldFile):
    """
    See ImageWithThumbsField for usage example
    """
    def __init__(self, *args, **kwargs):
        super(ImageWithThumbsFieldFile, self).__init__(*args, **kwargs)

        if self.field.sizes:
            def get_size(self, size):
                if not self:
                    return ''
                else:
                    split = self.url.rsplit('.', 1)
                    thumb_url = '%s.%sx%s.%s' % (split[0], w, h, split[1])
                    return thumb_url

            for size in self.field.sizes:
                (w, h) = size
                setattr(self, 'url_%sx%s' % (w, h), get_size(self, size))

    def save(self, name, content, save=True):
        super(ImageWithThumbsFieldFile, self).save(name, content, save)

        if self.field.sizes:
            for size in self.field.sizes:
                (w, h) = size
                split = self.name.rsplit('.', 1)
                thumb_name = '%s.%sx%s.%s' % (split[0], w, h, split[1])

                # you can use another thumbnailing function if you like
                thumb_content = generate_thumb(content, size, split[1])

                thumb_name_ = self.storage.save(thumb_name, thumb_content)

                if not thumb_name == thumb_name_:
                    raise ValueError(
                            'There is already a file named %s' % thumb_name)

    def delete(self, save=True):
        name = self.name
        super(ImageWithThumbsFieldFile, self).delete(save)
        if self.field.sizes:
            for size in self.field.sizes:
                (w, h) = size
                split = name.rsplit('.', 1)
                thumb_name = '%s.%sx%s.%s' % (split[0], w, h, split[1])
                try:
                    self.storage.delete(thumb_name)
                except:
                    pass

class ImageWithThumbsField(ImageField):
    attr_class = ImageWithThumbsFieldFile
    """
    Usage example:
    ==============
    photo = ImageWithThumbsField(
            upload_to='images', sizes=((125,125),(300,200),)

    To retrieve image URL, exactly the same way as with ImageField:
        my_object.photo.url
    To retrieve thumbnails URL's just add the size to it:
        my_object.photo.url_125x125
        my_object.photo.url_300x200

    Note: The 'sizes' attribute is not required. If you don't provide it,
    ImageWithThumbsField will act as a normal ImageField

    How it works:
    =============
    For each size in the 'sizes' atribute of the field it generates a
    thumbnail with that size and stores it following this format:

    available_filename.[width]x[height].extension

    Where 'available_filename' is the available filename returned by the
    storage backend for saving the original file.

    Following the usage example above: For storing a file called "photo.jpg"
    it saves:
    photo.jpg          (original file)
    photo.125x125.jpg  (first thumbnail)
    photo.300x200.jpg  (second thumbnail)

    With the default storage backend if photo.jpg already exists it will
    use these filenames:
    photo_.jpg
    photo_.125x125.jpg
    photo_.300x200.jpg

    Note: django-thumbs assumes that if filename "any_filename.jpg" is
    available filenames with this format "any_filename.[widht]x[height].jpg"
    will be available, too.

    To do:
    ======
    Add method to regenerate thubmnails

    """
    def __init__(self, verbose_name=None, name=None,
            width_field=None, height_field=None, sizes=None, **kwargs):
        self.verbose_name = verbose_name
        self.name = name
        self.width_field = width_field
        self.height_field = height_field
        self.sizes = sizes
        super(ImageField, self).__init__(**kwargs)
<|endoftext|>


<commit_before>import logging
import re
from functools import partial
import discord
from discord.ext import commands
import os
import asyncio
from ..tools import Cog
from ..tools import get_guild_config
from youtube_dl import YoutubeDL

log = logging.getLogger(__name__)
tiktok = re.compile(r'https?://vm\.tiktok\.com/.{6,}/')
opts = {'outtmpl': 'out.mp4'}
ytdl = YoutubeDL(opts)

class TikTok(Cog):
    active = False

    @staticmethod
    def download_tiktok(url):
        return ytdl.download([url])

    @commands.Cog.listener()
    async def on_message(self, message):
        if message.guild is None:
            return
        if (await get_guild_config(self.bot, message.guild.id))['tikTok'] is False:
            return
        matches = tiktok.match(message.content)
        if matches is None:
            return
        match = matches.group(0)
        log.info(f'collecting {match} for {message.author}')

        while self.active:
            await asyncio.sleep(.5)
        try:
            self.active = True
            if match:
                await message.channel.trigger_typing()
                await message.add_reaction('⌛')
                thing = partial(self.download_tiktok, match)
                await self.bot.loop.run_in_executor(None, thing)
                if os.path.getsize('out.mp4') > 8000000:
                    await message.remove_reaction('⌛')
                    await message.add_reaction('❌')
                else:
                    # file is out.mp4, need to create discord.File and upload it to channel then delete out.mp4
                    file = discord.File('out.mp4', 'tiktok.mp4')
                    await message.channel.send(file=file)
                    await message.remove_reaction('⌛')
                    await message.add_reaction('✅')

        finally:
            os.remove('out.mp4')
            self.active = False

def setup(bot):
    bot.add_cog(TikTok(bot))
<commit_msg>message.remove_reaction takes two args<commit_after>import logging
import re
from functools import partial
import discord
from discord.ext import commands
import os
import asyncio
from ..tools import Cog
from ..tools import get_guild_config
from youtube_dl import YoutubeDL

log = logging.getLogger(__name__)
tiktok = re.compile(r'https?://vm\.tiktok\.com/.{6,}/')
opts = {'outtmpl': 'out.mp4'}
ytdl = YoutubeDL(opts)

class TikTok(Cog):
    active = False

    @staticmethod
    def download_tiktok(url):
        return ytdl.download([url])

    @commands.Cog.listener()
    async def on_message(self, message: discord.Message):
        if message.guild is None:
            return
        if (await get_guild_config(self.bot, message.guild.id))['tikTok'] is False:
            return
        matches = tiktok.match(message.content)
        if matches is None:
            return
        match = matches.group(0)
        log.info(f'collecting {match} for {message.author}')

        while self.active:
            await asyncio.sleep(.5)
        try:
            self.active = True
            if match:
                await message.channel.trigger_typing()
                await message.add_reaction('⌛')
                thing = partial(self.download_tiktok, match)
                await self.bot.loop.run_in_executor(None, thing)
                if os.path.getsize('out.mp4') > 8000000:
                    await message.remove_reaction('⌛', self.bot.user)
                    await message.add_reaction('❌')
                else:
                    # file is out.mp4, need to create discord.File and upload it to channel then delete out.mp4
                    file = discord.File('out.mp4', 'tiktok.mp4')
                    await message.channel.send(file=file)
                    await message.remove_reaction('⌛', self.bot.user)
                    await message.add_reaction('✅')

        finally:
            os.remove('out.mp4')
            self.active = False

def setup(bot):
    bot.add_cog(TikTok(bot))
<|endoftext|>


<commit_before>from django import template
from django.db import models
from django.contrib.sites.models import Site

from django.template import Context, loader

register = template.Library()
Analytics = models.get_model('google_analytics', 'analytic')

def do_get_analytics(parser, token):
    try:
        # split_contents() knows not to split quoted strings.
        tag_name, code = token.split_contents()
    except ValueError:
        code = None

    if not code:
        current_site = Site.objects.get_current()
    else:
        if not (code[0] == code[-1] and code[0] in ('"', "'")):
            raise template.TemplateSyntaxError, "%r tag's argument should be in quotes" % tag_name
        code = code[1:-1]
        current_site = None
    return AnalyticsNode(current_site, code)

class AnalyticsNode(template.Node):
    def __init__(self, site=None, code=None):
        self.site = site
        self.code = code

    def render(self, context):
        content = ''
        if self.site:
            code_set = Analytics.objects.filter(site=self.site)
            if code_set:
                code = code_set[0].analytics_code
            else:
                return ''
        elif self.code:
            code = self.code
        else:
            return ''

        if code.strip() != '':
            t = loader.get_template('google_analytics/analytics_template.html')
            c = Context({
                'analytics_code': code,
                'domain': '.'.join(self.site.domain.split('.')[2:])
            })
            return t.render(c)
        else:
            return ''

register.tag('analytics', do_get_analytics)
<commit_msg>Fix top-level domain extraction from site name. Reviewed by milo.<commit_after>from django import template
from django.db import models
from django.contrib.sites.models import Site

from django.template import Context, loader

register = template.Library()
Analytics = models.get_model('google_analytics', 'analytic')

def do_get_analytics(parser, token):
    try:
        # split_contents() knows not to split quoted strings.
        tag_name, code = token.split_contents()
    except ValueError:
        code = None

    if not code:
        current_site = Site.objects.get_current()
    else:
        if not (code[0] == code[-1] and code[0] in ('"', "'")):
            raise template.TemplateSyntaxError, "%r tag's argument should be in quotes" % tag_name
        code = code[1:-1]
        current_site = None
    return AnalyticsNode(current_site, code)

class AnalyticsNode(template.Node):
    def __init__(self, site=None, code=None):
        self.site = site
        self.code = code

    def render(self, context):
        content = ''
        if self.site:
            code_set = Analytics.objects.filter(site=self.site)
            if code_set:
                code = code_set[0].analytics_code
            else:
                return ''
        elif self.code:
            code = self.code
        else:
            return ''

        if code.strip() != '':
            t = loader.get_template('google_analytics/analytics_template.html')
            c = Context({
                'analytics_code': code,
                'domain': '.'.join(self.site.domain.split('.')[-2:])
            })
            return t.render(c)
        else:
            return ''

register.tag('analytics', do_get_analytics)
<|endoftext|>


<commit_before># WHAT: Analyze how I spent my time
    # Two main functions:
    # 1. Print table of hrs spent - by day, by categ
    # 2. Print selected events - chosen by day/catg/keyword

# HOW: Takes in log, processes, then prints tables
    # Below is info on the formatting of the time log
    # Check 'timeLog_example.txt' for a concrete example
    # 1. Must start with 'month year', eg. 'May 2015'
    # 2. Must proceed with 'day number', eg. 'Tues 5'
    # 3. First event for each day must be when woke up
    # 4. For each event: 'time desc_categ', eg. '940 wakeup_fff'
        # Note that '940' is '9:40am' and it's the END time
        # Special abbrev: after '940' a '50' means '9:50am'
        # Also use military time, so '2140' means '9:40pm'
    # That's it!

# WHY: To work more on high priority tasks
    # To see how I spend my time, just like how I spend money
    # Each use of time is like a 'purchase'
    # Just tracking makes me aware of how I spend
    # 1. What events are fixed (eg. sleep) vs discretionary (eg. fb)?
        # Breaks are impt to avoid burnout - so they're inbetween
    # 2. How much value am I getting from each event?
        # What events are great and I should do more of?
        # What events are worthless and I should avoid?
    # 3. How long did I think it'd take? How long did it actually take?
    # 4. How much do I plan on working? How much do I actually work?
    # Overall.
        # Spend 1hr on exercising and meditation
        # Spend 1hr on breaks
        # Spend 5hr on tasks
        # Minimize time socializing
        # Do more higher priority tasks over low

# NOTE: Ugly right now, but works. Will refactor later
# TODO:
    # Refactor, turn this whole thing into a class (no more globals)
    # Could use fancier datetime functions, just for practice
        #from datetime import datetime
        # ESP. for "June" to "5"

def addData_toTable(dayData):
    # Use a dict, fill with placeholders for table later
    dct = {}
    for ch in ['t','b','f']:
        for i in (1,2,3):
            dct[ch*i] = 0
    dct['mis'] = 0
    dct['org'] = 0

    for event in dayData:
        categ = event[3]
        duration = event[4]
        if categ not in dct:
            dct[categ] = duration
        else:
            dct[categ] += duration
        desc = event[2]
        if 'orgz' in desc:
            dct['org'] += duration

    daySum = 0
    for key in dct:
        if key in table:
            table[key].append(dct[key])
        else:
            table[key] =[dct[key]]
        if key != 'org':
            daySum += dct[key]
    table['sum'].append(daySum)

def printTable(table):
    # Print day
    print 'day,',
    for i in table['day']:
        print '%04s'%i + ',',
    print
    print

    # Print each categ
    for ch in ['t','b','f']:
        for i in (1,2,3):
            categ = ch*i
            if categ != 'fff':
                print format(categ,'3') + ',',
                for i in table[categ]:
                    print "{:4.1f}".format(i) + ',',
                print
        print

    # Print extra categ
    for label in ['org','sum','mis']:
        print label + ',',
        for i in table[label]:
            print "{:4.1f}".format(i) + ',',
        print

    print

#### Grab raw data and put into form I want!
logDir = "C:/Users/Abe/Dropbox/CS/apps/analyzeLog/"
#logFile = "timeLog.txt"
logFile = "timeLog_quick.txt"
#logFile = "timeLog_example.txt"
f =  open(logDir + logFile,"r")
curr = 0.0
events = []
## Crazy main for-loop
for line in f:
    event = []
    line =line.strip()
    if line == 'log' or line == '':
        pass
    elif line[0] == '$':
        #strMonthYear = line[1:]
        strMonth =line[1:].split(' ')[0]
        #TODO: do this automatically, yo
        if strMonth == 'May':
            strMonth = '5'
        elif strMonth == 'June':
            strMonth = '6'
    elif line[0] == '#':
        # Grab just number of the day (ie. '5' instead of 'Tues')
        strDay = line[1:].split(' ')[1]
    else:
        # Split events into 3 parts => time, desc, categ
        # Use '_' as the delimiter. Change first space to '_'
        event.append('/'.join([strMonth,strDay]))
        event += line.replace(' ','_',1).split('_',2)
        if len(event) < 4:
            event.append('mis')

        # Get duration
        # Start with hours and mins
        time = event[1]
        if len(time) == 2:
            currMin = time
        elif len(time) == 3:
            currHour = time[0]
            currMin = time[1:]
        elif len(time) == 4:
            currHour = time[0:2]
            currMin = time[2:]
        past = curr
        curr = float(currHour) + float(currMin)/60
        duration = curr - past

        # Deal with times that cross over days
        if duration < 0:
            duration += 24
        event.append(duration)

        events.append(event)
f.close()
# Marks end (for making table later)
events.append(['end','','','',''])

#### Take data and tabulate by day & categ
## Make output table below
table = {}
table['day'] = []
table['sum'] = []
curr = ''
dayData =[]
#TODO: Could shorten this using a range, or a while loop?
    # Like maybe take out need for 'end' above
    # Maybe a dumb idea
for line in events:
    past = curr
    curr = line[0]
    if curr != past:
        if len(dayData) > 0:
            table['day'].append(past)
            addData_toTable(dayData)
            dayData =[]
    else:
        dayData.append(line)

printTable(table)

def printEvents(events,categ='',day='',include='',exclude=''):
    viewSum = 0
    for line in events:
        if line[0]=='end':
            continue
        elif day!='' and line[0] != day:
            continue
        elif categ!='' and line[3] != categ:
            continue
        elif include!='' and include not in line[2]:
            continue
        elif exclude!='' and exclude in line[2]:
            continue
        print line[0],
        print line[3],
        print "{:4.1f}".format(line[4]),
        print line[2]
        viewSum += line[4]
    print "sum:","{:4.1f}".format(viewSum)

printEvents(events,categ='tt')
#printEvents(events,categ='tt',exclude='analyzeLog')
#printEvents(events,include='orgz')

<commit_msg>!Refactoring: Turned into class<commit_after># WHAT: Analyze how I spent my time
    # Two main functions:
    # 1. Print table of hrs spent - by day, by categ
    # 2. Print selected events - chosen by day/catg/keyword

# HOW: Takes in log, processes it, then prints tables
    # Below is info on the formatting of the time log
    # Check 'timeLog_example.txt' for a concrete example
    # 1. Must start with 'month year', eg. 'May 2015'
    # 2. Must proceed with 'day number', eg. 'Tues 5'
    # 3. First event for each day must be when woke up
    # 4. For each event: 'time desc_categ', eg. '940 wakeup_fff'
        # Note that '940' is '9:40am' and it's the END time
        # Special abbrev: after '940' a '50' means '9:50am'
        # Also use military time, so '2140' means '9:40pm'

# WHY: To work more on high priority tasks
    # To see how I spend my time, just like how I spend money
    # Each use of time is like a 'purchase'
    # Just tracking makes me aware of how I spend
    # 1. What events are fixed (eg. sleep) vs discretionary (eg. fb)?
        # Breaks are impt to avoid burnout - so they're inbetween
    # 2. How much value am I getting from each event?
        # What events are great and I should do more of?
        # What events are worthless and I should avoid?
    # 3. How long did I think it'd take? How long did it actually take?
    # 4. How much do I plan on working? How much do I actually work?
    # Overall.
        # Spend 1hr on exercising and meditation
        # Spend 1hr on breaks
        # Spend 5hr on great tasks (over lesser tasks)
        # Minimize time socializing

# TODO:
    # Need a bit more cleaning up - esp. 'crazy main loop'
        # getDuration as its own fctn
        # find way to convert 'June' to '6'
    # Could use fancier datetime functions, just for practice
        #from datetime import datetime
        # ESP. for "June" to "5"

from datetime import datetime

class TimeTracker:
    def __init__(self):
        self.events = []
        self.table = {}

    def extractData(self,logLocation):
        hr = 0.0
        clock = 0.0

        ## Crazy main for-loop
        f =  open(logLocation,"r")
        for line in f:
            event = []
            line =line.strip()
            if line == 'log' or line == '':
                pass

            elif line[0] == '$':
                month = str(datetime.strptime(line,'$%b %Y').month)

            elif line[0] == '#':
                day = line[1:]

            else:
                event.append(month + '/' + day)

                # For events, use '_' as the delimiter. Change first space to '_'
                # Split events into 3 parts => time, desc, categ
                event += line.replace(' ','_',1).split('_',2)
                if len(event) < 4:
                    event.append('mis')

                duration, hr, clock = self.getDuration(event[1],hr,clock)
                event.append(duration)

                self.events.append(event)
        f.close()
        # Marks end (for makeTable later)
        self.events.append(['end','na','na','na',0])

    def getDuration(self, logged, prevHr, prevClock):
        # Identify hr and mins:
        # eg. '930' is '9:30am' and '2130' is '9:30pm'
        # while '50' after '930' means '9:50am'
        if len(logged) == 2:
            hr = prevHr
            mins = logged
        elif len(logged) == 3:
            hr = logged[0]
            mins = logged[1:]
        elif len(logged) == 4:
            hr = logged[0:2]
            mins = logged[2:]

        clock = float(hr) + float(mins)/60
        duration = clock - prevClock

        # For event that goes past midnight (eg. 2359 to 000)
        if duration < 0:
            duration += 24

        return (duration, hr, clock)

    def makeTable(self):
        #### Take data and tabulate by day & categ
        ## Make output table below

        self.table['day'] = []
        curr = ''
        daysData =[]
        for line in self.events:
            past = curr
            curr = line[0]
            if curr == past:
                daysData.append(line)
            elif daysData:
                self.table['day'].append(past)
                self.forTable_addData(daysData)
                daysData =[]

    def forTable_addData(self,daysData):
        ''' Grab stats for each day, then add to table '''

        # Fill with placeholder categs (in case, to fill out table)
        dct = {}
        for ch in ['t','b','f']:
            for i in (1,2,3):
                dct[ch*i] = 0
        dct['mis'] = 0
        dct['org'] = 0
        dct['sum'] = 0

        # Stats: Time spent in each categ
        # That is, for each event, add its dur to its categ
        # Note: Each event has only one categ => mutually exclusive
        for event in daysData:
            desc = event[2]
            categ = event[3]
            duration = event[4]

            dct[categ] += duration
            dct['sum'] += duration

            # Special categ if event matches substr 'orgz'
            # So *not* mutually excl with above
            if 'orgz' in desc:
                dct['org'] += duration

        # Now add to table
        for key in dct:
            if key in self.table:
                self.table[key].append(dct[key])
            else:
                self.table[key] = [dct[key]]

    def printTable(self):
        # Print month/day
        print 'day',
        for i in self.table['day']:
            print "{:>5}".format(i),
        print
        print

        # Print each categ
        for ch in ['t','b','f']:
            for i in (1,2,3):
                categ = ch*i
                if categ != 'fff':
                    print format(categ,'3'),
                    for i in self.table[categ]:
                        print "{:5.1f}".format(i),
                    print
            print

        # Print extra categs
        for categ in ['org','sum','mis']:
            print categ,
            for i in self.table[categ]:
                print "{:5.1f}".format(i),
            print

        print

    def printEvents(self, categ='',day='',include='',exclude=''):
        # Note: Dislike name 'day0' but best bad
        viewSum = 0
        for event in self.events:
            day0 = event[0]
            desc0 = event[2]
            categ0 = event[3]
            dur0 = event[4]

            # Enact options below
            if 'end' == day0:
                continue
            elif day and day != day0:
                continue
            elif categ and categ != categ0:
                continue
            elif include and include not in desc0:
                continue
            elif exclude and exclude in desc0:
                continue

            # Print day, dur, categ, desc
            print "{:>5}".format(day0),
            print "{:4.1f}".format(dur0),
            print "{:3}".format(categ0),
            print desc0
            viewSum += dur0

        print "SUM:","{:4.1f}".format(viewSum)

def main():
    logDir = "C:/Users/Abe/Dropbox/CS/apps/analyzeLog/"
    logFile = "timeLog.txt"
    t = TimeTracker()
    t.extractData(logDir + logFile)
    t.makeTable()
    t.printTable()

    t.printEvents()
    #t.printEvents(day='5/9',categ='tt')
    #t.printEvents(day='5/9',categ='tt',exclude='analyzeLog')
    #t.printEvents(include='orgz')

main()

<|endoftext|>


<commit_before>'''

command line arguements :-
	now	-	get todays weather update
	-l	-	location

'''

import urllib, urllib2, json
import os
import sys
from datetime import datetime

class bcolors:
    HEADER = '\033[95m'
    OKBLUE = '\033[94m'   # BLUE
    OKGREEN = '\033[92m'  # GREEN
    WARNING = '\033[93m'  # YELLOW
    FAIL = '\033[91m'
    ENDC = '\033[0m'
    BOLD = '\033[1m'
    UNDERLINE = '\033[4m'

dir_path = os.path.expanduser('~')

def init_wipy():
	dirp = dir_path + "/.wipy"
	if not os.path.isdir(dirp):
		os.makedirs(dirp)

def query(location):
	url = "https://query.yahooapis.com/v1/public/yql?"
	query = "select * from weather.forecast where woeid in (select woeid from geo.places(1) where text='%s')" % location
	q_url = url + urllib.urlencode({'q': query}) + "&format=json"
	result = json.loads(urllib2.urlopen(q_url).read())
	return result

init_wipy()

no_location = False
want_current_data = True

args = sys.argv[1:]
no_args = False
if len(args) == 0:
	no_args = True
location = 'lucknow';
if '-l' not in args:
	no_location = True
else:
	location = args[args.index('-l') + 1]
if 'now' not in args:
	want_current_data = False

date = str(datetime.now().date())
has_current_data = True
if not os.path.exists(dir_path + "/.wipy/" + date):
	has_current_data = False

bc = bcolors()

result = query(location)

location_json = result['query']['results']['channel']['location']
wind_json = result['query']['results']['channel']['wind']
sun_json = result['query']['results']['channel']['astronomy']
atmos_json = result['query']['results']['channel']['atmosphere']

item_json = result['query']['results']['channel']['item']

condition_json = item_json['condition'];
forecast_array = item_json['forecast']

#       PRINT LOCATION		#

print "City - %s\nRegion - %s\nCountry - %s\n\n" % (location_json['city'], location_json['region'], location_json['country'])

#		PRINT WIND CONDITION		#
if '-w' in args and not no_args:
	print "Wind Temp - %s F\nDirection - %s\nSpeed - %skmph\n\n" % (wind_json['chill'], wind_json['direction'], wind_json['speed'])

#		SUN SETTINGS			#
if '-s' in args and not no_args:
	print "Sunrise - %s\nSunset - %s\n\n" % (sun_json['sunrise'], sun_json['sunset'])

#		ATMOSPHERE			#
if '-a' in args and not no_args:
	print "Humidity - %s percent\nPressure - %s mb\nVisibility - %sm\n\n" % (atmos_json['humidity'], atmos_json['pressure'], atmos_json['visibility'])

#		WEATHER CONDITION		#
if '-we' in args and not no_args:
	print "Weather Temp - %s F\nWeather - %s\n\n" % (condition_json['temp'], condition_json['text'])

#		FORECAST			#
if '-f' in args and not no_args:
	print bc.OKBLUE + "Weather Forecast" + bc.ENDC + "\n"
	xdate = int(args[args.index('-f') + 1])
	for f in forecast_array[0:xdate]:
		print "Date - %s %s\nHigh. Temp - %s\nLow. Temp - %s\nWeather - %s\n\n" % (f['date'], f['day'], f['high'], f['low'], f['text'])
<commit_msg>remove w<commit_after><|endoftext|>


<commit_before>"""
============================
pre-processing algorithm
============================

"""

from __future__ import division

import os

import pandas as pd

import cea.optimization.preprocessing.processheat as process_heat
from cea.optimization.master import summarize_network
from cea.optimization.preprocessing import electricity
from cea.resources import geothermal
from cea.utilities import  epwreader
from cea.technologies import substation
from cea.optimization.preprocessing import decentralized_buildings

__author__ = "Jimeno A. Fonseca"
__copyright__ = "Copyright 2015, Architecture and Building Systems - ETH Zurich"
__credits__ = ["Jimeno A. Fonseca", "Thuy-An Nguyen", "Tim Vollrath"]
__license__ = "MIT"
__version__ = "0.1"
__maintainer__ = "Daren Thomas"
__email__ = "thomas@arch.ethz.ch"
__status__ = "Production"

def preproccessing(locator, total_demand, building_names, weather_file, gv):
    """
    This function aims at preprocessing all data for the optimization.

    :param locator: path to locator function
    :param total_demand: dataframe with total demand and names of all building in the area
    :param building_names: dataframe with names of all buildings in the area
    :param weather_file: path to wather file
    :param gv: path to global variables class
    :type locator: class
    :type total_demand: list
    :type building_names: list
    :type weather_file: string
    :type gv: class
    :return: extraCosts: extra pareto optimal costs due to electricity and process heat (
             these are treated separately and not considered inside the optimization)
             extraCO2: extra pareto optimal emissions due to electricity and process heat (
             these are treated separately and not considered inside the optimization)
             extraPrim: extra pareto optimal primary energy due to electricity and process heat (
             these are treated separately and not considered inside the optimization)
             solar_features: extraction of solar features form the results of the solar technologies
             calculation.
    :rtype: float, float, float, float
    """

    # GET ENERGY POTENTIALS
    # geothermal
    T_ambient = epwreader.epw_reader(weather_file)['drybulb_C']
    gv.ground_temperature = geothermal.calc_ground_temperature(T_ambient.values, gv)

    # solar
    print "Solar features extraction"
    solar_features = SolarFeatures(locator)

    # GET LOADS IN SUBSTATIONS
    # prepocess space heating, domestic hot water and space cooling to substation.
    print "Run substation model for each building separately"
    substation.substation_main(locator, total_demand, building_names, gv, Flag = True) # True if disconected buildings are calculated

    # GET COMPETITIVE ALTERNATIVES TO A NETWORK
    # estimate what would be the operation of single buildings only for heating.
    # For cooling all buildings are assumed to be connected to the cooling distribution on site.
    print "Run decentralized model for buildings"
    decentralized_buildings.decentralized_main(locator, building_names, gv)

    # GET DH NETWORK
    # at first estimate a distribution with all the buildings connected at it.
    print "Create distribution file with all buildings connected"
    summarize_network.network_main(locator, total_demand, building_names, gv, "all") #"_all" key for all buildings

    # GET EXTRAS
    # estimate the extra costs, emissions and primary energy of electricity.
    print "electricity"
    elecCosts, elecCO2, elecPrim = electricity.calc_pareto_electricity(locator, gv)

    # estimate the extra costs, emissions and primary energy for process heat
    print "Process-heat"
    hpCosts, hpCO2, hpPrim = process_heat.calc_pareto_Qhp(locator, total_demand, gv)

    extraCosts = elecCosts + hpCosts
    extraCO2 = elecCO2 + hpCO2
    extraPrim = elecPrim + hpPrim

    return extraCosts, extraCO2, extraPrim, solar_features

class SolarFeatures(object):
    def __init__(self, locator):
        self.PV_Peak = pd.read_csv(os.path.join(locator.get_potentials_solar_folder(), "Pv.csv"), usecols=["PV_kWh"]).values.max()
        self.SolarAreaPV = pd.read_csv(os.path.join(locator.get_potentials_solar_folder(), "Pv.csv"), usecols=["Area"]).values.max()
        self.PVT_Peak = pd.read_csv(os.path.join(locator.get_potentials_solar_folder(), "PVT_35.csv"), usecols=["PV_kWh"]).values.max()
        self.PVT_Qnom = pd.read_csv(os.path.join(locator.get_potentials_solar_folder(), "PVT_35.csv"), usecols=["Qsc_KWh"]).values.max()*1000
        self.SolarAreaPVT = pd.read_csv(os.path.join(locator.get_potentials_solar_folder(), "PVT_35.csv"), usecols=["Area"]).values.max()
        self.SC_Qnom = pd.read_csv(os.path.join(locator.get_potentials_solar_folder(), "SC_75.csv"), usecols=["Qsc_Kw"]).values.max()* 1000
        self.SolarAreaSC = pd.read_csv(os.path.join(locator.get_potentials_solar_folder(), "SC_75.csv"), usecols=["Area"]).values.max()

#============================
#test
#============================

def run_as_script(scenario_path=None):
    """
    run the whole preprocessing routine
    """
    import cea.globalvar

    gv = cea.globalvar.GlobalVariables()

    if scenario_path is None:
        scenario_path = gv.scenario_reference

    locator = cea.inputlocator.InputLocator(scenario_path=scenario_path)
    total_demand = pd.read_csv(locator.get_total_demand())
    building_names = pd.read_csv(locator.get_total_demand())['Name']
    weather_file = locator.get_default_weather()
    preproccessing(locator, total_demand, building_names, weather_file, gv)

    print 'test_preprocessing_main() succeeded'

if __name__ == '__main__':
    run_as_script()

<commit_msg>minor changes following pep8<commit_after>"""
============================
pre-processing algorithm
============================

"""

from __future__ import division

import os

import pandas as pd

import cea.optimization.preprocessing.processheat as process_heat
from cea.optimization.master import summarize_network
from cea.optimization.preprocessing import electricity
from cea.resources import geothermal
from cea.utilities import epwreader
from cea.technologies import substation
from cea.optimization.preprocessing import decentralized_buildings

__author__ = "Jimeno A. Fonseca"
__copyright__ = "Copyright 2015, Architecture and Building Systems - ETH Zurich"
__credits__ = ["Jimeno A. Fonseca", "Thuy-An Nguyen", "Tim Vollrath"]
__license__ = "MIT"
__version__ = "0.1"
__maintainer__ = "Daren Thomas"
__email__ = "thomas@arch.ethz.ch"
__status__ = "Production"

def preproccessing(locator, total_demand, building_names, weather_file, gv):
    """
    This function aims at preprocessing all data for the optimization.

    :param locator: path to locator function
    :param total_demand: dataframe with total demand and names of all building in the area
    :param building_names: dataframe with names of all buildings in the area
    :param weather_file: path to wather file
    :param gv: path to global variables class
    :type locator: class
    :type total_demand: list
    :type building_names: list
    :type weather_file: string
    :type gv: class
    :return: extraCosts: extra pareto optimal costs due to electricity and process heat (
             these are treated separately and not considered inside the optimization)
             extraCO2: extra pareto optimal emissions due to electricity and process heat (
             these are treated separately and not considered inside the optimization)
             extraPrim: extra pareto optimal primary energy due to electricity and process heat (
             these are treated separately and not considered inside the optimization)
             solar_features: extraction of solar features form the results of the solar technologies
             calculation.
    :rtype: float, float, float, float
    """

    # GET ENERGY POTENTIALS
    # geothermal
    T_ambient = epwreader.epw_reader(weather_file)['drybulb_C']
    gv.ground_temperature = geothermal.calc_ground_temperature(T_ambient.values, gv)

    # solar
    print "Solar features extraction"
    solar_features = SolarFeatures(locator)

    # GET LOADS IN SUBSTATIONS
    # prepocess space heating, domestic hot water and space cooling to substation.
    print "Run substation model for each building separately"
    substation.substation_main(locator, total_demand, building_names, gv, Flag = True) # True if disconected buildings are calculated

    # GET COMPETITIVE ALTERNATIVES TO A NETWORK
    # estimate what would be the operation of single buildings only for heating.
    # For cooling all buildings are assumed to be connected to the cooling distribution on site.
    print "Run decentralized model for buildings"
    decentralized_buildings.decentralized_main(locator, building_names, gv)

    # GET DH NETWORK
    # at first estimate a distribution with all the buildings connected at it.
    print "Create distribution file with all buildings connected"
    summarize_network.network_main(locator, total_demand, building_names, gv, "all") #"_all" key for all buildings

    # GET EXTRAS
    # estimate the extra costs, emissions and primary energy of electricity.
    print "electricity"
    elecCosts, elecCO2, elecPrim = electricity.calc_pareto_electricity(locator, gv)

    # estimate the extra costs, emissions and primary energy for process heat
    print "Process-heat"
    hpCosts, hpCO2, hpPrim = process_heat.calc_pareto_Qhp(locator, total_demand, gv)

    extraCosts = elecCosts + hpCosts
    extraCO2 = elecCO2 + hpCO2
    extraPrim = elecPrim + hpPrim

    return extraCosts, extraCO2, extraPrim, solar_features

class SolarFeatures(object):
    def __init__(self, locator):
        self.PV_Peak = pd.read_csv(os.path.join(locator.get_potentials_solar_folder(), "Pv.csv"), usecols=["PV_kWh"]).values.max()
        self.SolarAreaPV = pd.read_csv(os.path.join(locator.get_potentials_solar_folder(), "Pv.csv"), usecols=["Area"]).values.max()
        self.PVT_Peak = pd.read_csv(os.path.join(locator.get_potentials_solar_folder(), "PVT_35.csv"), usecols=["PV_kWh"]).values.max()
        self.PVT_Qnom = pd.read_csv(os.path.join(locator.get_potentials_solar_folder(), "PVT_35.csv"), usecols=["Qsc_KWh"]).values.max()*1000
        self.SolarAreaPVT = pd.read_csv(os.path.join(locator.get_potentials_solar_folder(), "PVT_35.csv"), usecols=["Area"]).values.max()
        self.SC_Qnom = pd.read_csv(os.path.join(locator.get_potentials_solar_folder(), "SC_75.csv"), usecols=["Qsc_Kw"]).values.max()* 1000
        self.SolarAreaSC = pd.read_csv(os.path.join(locator.get_potentials_solar_folder(), "SC_75.csv"), usecols=["Area"]).values.max()

#============================
#test
#============================

def run_as_script(scenario_path=None):
    """
    run the whole preprocessing routine
    """
    import cea.globalvar

    gv = cea.globalvar.GlobalVariables()

    if scenario_path is None:
        scenario_path = gv.scenario_reference

    locator = cea.inputlocator.InputLocator(scenario_path=scenario_path)
    total_demand = pd.read_csv(locator.get_total_demand())
    building_names = pd.read_csv(locator.get_total_demand())['Name']
    weather_file = locator.get_default_weather()
    preproccessing(locator, total_demand, building_names, weather_file, gv)

    print 'test_preprocessing_main() succeeded'

if __name__ == '__main__':
    run_as_script()

<|endoftext|>


<commit_before>#!/usr/bin/python
# -*- coding: utf-8 -*-

# Hive Netius System
# Copyright (c) 2008-2016 Hive Solutions Lda.
#
# This file is part of Hive Netius System.
#
# Hive Netius System is free software: you can redistribute it and/or modify
# it under the terms of the Apache License as published by the Apache
# Foundation, either version 2.0 of the License, or (at your option) any
# later version.
#
# Hive Netius System is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# Apache License for more details.
#
# You should have received a copy of the Apache License along with
# Hive Netius System. If not, see <http://www.apache.org/licenses/>.

__author__ = "João Magalhães <joamag@hive.pt>"
""" The author(s) of the module """

__version__ = "1.0.0"
""" The version of the module """

__revision__ = "$LastChangedRevision$"
""" The revision number of the module """

__date__ = "$LastChangedDate$"
""" The last change date of the module """

__copyright__ = "Copyright (c) 2008-2016 Hive Solutions Lda."
""" The copyright for the module """

__license__ = "Apache License, Version 2.0"
""" The license for the module """

import re
import time

import netius.common
import netius.servers

class ReverseProxyServer(netius.servers.ProxyServer):
    """
    Reverse HTTP proxy implementation based on the more generalized
    infra-structure of proxy server.

    Supports multiple scheduling strategies, being the "smart" one
    the most efficient for most of the possible scenarios.

    Conditional re-usage of connections based on boolean flag is possible,
    but care should be taken when using re-usage so that no multiple
    rules are applied for the same connection (eg: https://host.com/hive,
    https://host.com/colony as different rules), this would pose serious
    problems if the back-end servers are different for each rule or if
    the way the final back-end url is created is different for each rule.
    """

    def __init__(
        self,
        config = "proxy.json",
        regex = {},
        hosts = {},
        auth = {},
        redirect = {},
        strategy = "smart",
        reuse = True,
        *args,
        **kwargs
    ):
        netius.servers.ProxyServer.__init__(self, *args, **kwargs)
        if type(regex) == dict: regex = regex.items()
        if not type(hosts) == dict: hosts = dict(hosts)
        self.load_config(
            path = config,
            regex = regex,
            hosts = hosts,
            auth = auth,
            redirect = redirect,
            strategy = strategy,
            reuse = reuse,
            robin = dict(),
            smart = netius.common.PriorityDict()
        )
        self.busy_conn = 0
        self.balancer_m = getattr(self, "balancer_" + self.strategy)
        self.acquirer_m = getattr(self, "acquirer_" + self.strategy)
        self.releaser_m = getattr(self, "releaser_" + self.strategy)

    def info_dict(self, full = False):
        info = netius.servers.ProxyServer.info_dict(self, full = full)
        info.update(
            reuse = self.reuse,
            strategy = self.strategy,
            busy_conn = self.busy_conn
        )
        return info

    def on_headers(self, connection, parser):
        netius.servers.ProxyServer.on_headers(self, connection, parser)

        # retrieves the various parts/configuration values of the parser, that
        # are going to be used in the processing/routing of the proxy request,
        # note that some of these values need to be transformed to be used
        method = parser.method.upper()
        path = parser.path_s
        headers = parser.headers
        version_s = parser.version_s
        is_secure = connection.ssl
        host = headers.get("host", None)
        address = headers.get("x-forwarded-for", connection.address[0])
        protocol = headers.get("x-forwarded-proto", None)
        protocol = protocol or ("https" if is_secure else "http")

        # tries to determine if a proper (client side) redirection should operation
        # should be applied to the current request, if that's the case (match) an
        # immediate response is returned with proper redirection instructions
        redirect = self.redirect.get(host, None)
        if redirect:
            location = "%s://%s%s" % (protocol, redirect, path)
            connection.send_response(
                headers = dict(
                    location = location
                ),
                version = version_s,
                code = 303,
                code_s = "See Other",
                apply = True
            )
            return

        # tries to extract the various attributes of the current connection
        # that are going to be used for the routing operation, this attributes
        # should avoid a new rule setting operation (expensive) and provide
        # connection, prefix and state re-usage whenever possible, note that
        # under some situation the prefix may change for the same connection
        # and so the connection re-usage is not possible
        prefix = connection.prefix if hasattr(connection, "prefix") else None
        state = connection.state if hasattr(connection, "state") else None
        reusable = hasattr(connection, "proxy_c")

        # constructs the url that is going to be used by the rule engine and
        # then "forwards" both the url and the parser object to the rule engine
        # in order to obtain the possible prefix value for url reconstruction,
        # a state value is also retrieved, this value will be used latter for
        # the acquiring and releasing parts of the balancing strategy operation
        url = "%s://%s%s" % (protocol, host, path)
        if not self.reuse or not reusable: prefix, state = self.rules(url, parser)

        # in case no prefix is defined at this stage there's no matching
        # against the currently defined rules and so an error must be raised
        # and propagated to the client connection (end user notification)
        if not prefix:
            self.debug("No valid proxy endpoint found")
            connection.send_response(
                data = "No valid proxy endpoint found",
                headers = dict(
                    connection = "close"
                ),
                version = version_s,
                code = 404,
                code_s = "Not Found",
                apply = True,
                callback = self._prx_close
            )
            return

        # verifies if the current host requires come kind of special authorization
        # process using the default basic http authorization process
        auth = self.auth.get(host.split(":", 1)[0], None)
        if auth:
            # tries to retrieve the authorization header for the current request
            # and in case it's not defined "raises" a non authorized response
            authorization = headers.get("authorization", None)
            if not authorization:
                connection.send_response(
                    data = "Not authorized",
                    headers = {
                        "connection" : "close",
                        "wWW-authenticate" : "Basic realm=\"insert realm\""
                    },
                    version = version_s,
                    code = 401,
                    code_s = "Not Authorized",
                    apply = True,
                    callback = self._prx_close
                )
                return

            # runs the authorization process for the requested auth value (file path)
            # and using the current parser for the connection handling, this should
            # return a valid response in case the authorization is valid and an invalid
            # value otherwise, in case there's a failure a not authorized value is returned
            result = self.authorize(auth, parser)
            if not result:
                connection.send_response(
                    data = "Not authorized",
                    headers = {
                        "connection" : "close",
                        "wWW-authenticate" : "Basic realm=\"insert realm\""
                    },
                    version = version_s,
                    code = 401,
                    code_s = "Not Authorized",
                    apply = True,
                    callback = self._prx_close
                )
                return

        # runs the acquire operation for the current state, this should
        # update the current scheduling algorithm internal structures so
        # that they properly handle the new handle operation, an inverse
        # release operation should be performed at the end of the handling
        self.acquirer(state)

        # re-calculates the url for the reverse connection based on the
        # prefix value that has just been "resolved" using the rule engine
        # this value should be constructed based on the original path
        url = prefix + path

        # updates the various headers that are related with the reverse
        # proxy operation this is required so that the request gets
        # properly handled by the reverse server, otherwise some problems
        # would occur in the operation of handling the request
        headers["x-forwarded-for"] = address
        headers["x-forwarded-proto"] = protocol

        # verifies if the current connection already contains a valid
        # a proxy connection if that's the case that must be unset from
        # the connection and from the connection map internal structures
        # at least until the http client returns from the method call
        proxy_c = hasattr(connection, "proxy_c") and connection.proxy_c
        proxy_c = proxy_c or None
        connection.proxy_c = None
        if proxy_c in self.conn_map: del self.conn_map[proxy_c]

        # calls the proper (http) method in the client this should acquire
        # a new connection and start the process of sending the request
        # to the associated http server (request handling)
        _connection = self.http_client.method(
            method,
            url,
            headers = headers,
            version = version_s,
            encodings = None,
            connection = proxy_c
        )

        # sets the state attribute in the connection so that it's possible
        # to retrieve it latter for tagging evaluation, this is required for
        # advanced load balancing techniques to be performed
        _connection.state = state

        # sets the current connection as busy, as it's waiting for a message
        # to be returned from the the back-end side, then increments the number
        # of currently busy connections (as expected)
        _connection.busy = _connection.busy if hasattr(_connection, "busy") else 0
        _connection.busy += 1
        self.busy_conn += 1

        # prints a debug message about the connection becoming a waiting
        # connection meaning that the connection with the client host has
        # not been yet established (no data has been  received)
        self.debug("Setting connection as waiting, proxy connection loading ...")

        # sets the current http back-end client connection as waiting and then
        # maps it as the proxy connection in the connection and also creates
        # the reverse mapping using the connection map of the current server
        _connection.waiting = True
        connection.proxy_c = _connection
        connection.prefix = prefix
        connection.state = state
        self.conn_map[_connection] = connection

    def rules(self, url, parser):
        resolved = self.rules_regex(url, parser)
        if resolved[0]: return resolved
        resolved = self.rules_host(url, parser)
        if resolved[0]: return resolved
        return None, None

    def rules_regex(self, url, parser):
        # sets the prefix and state values initially for the invalid/unset value,
        # these values are going to be populated once a valid match is found
        prefix = None
        state = None

        # iterates over the complete set of defined regex values to try
        # to find a valid match and apply the groups value for format
        # if the complete chain of regex is processed but there's no
        # valid match the prefix value is considered to be unset
        for regex, _prefix in self.regex:
            match = regex.match(url)
            if not match: continue
            _prefix, _state = self.balancer(_prefix)
            groups = match.groups()
            if groups: _prefix = _prefix.format(*groups)
            prefix = _prefix
            state = _state
            break

        # returns the prefix and state values that have just been resolved
        # through regex based validation, this value may be unset for a mismatch
        return prefix, state

    def rules_host(self, url, parser):
        # retrieves the reference to the headers map from the parser so
        # that it may be used to retrieve the current host value and try
        # to match it against any of the currently defined rules
        headers = parser.headers

        # retrieves the host header from the received set of headers,
        # removing the port definition part of the host and then verifies
        # the complete set of defined hosts in order to check for the
        # presence of such rule, in case there's a match the defined
        # url prefix is going to be used instead, the balancer operation
        # is then used to "resolve" the final prefix value from sequence
        host = headers.get("host", None)
        if host: host = host.split(":", 1)[0]
        prefix = self.hosts.get(host, None)
        resolved = self.balancer(prefix)

        # returns the final "resolved" prefix value (in case there's any)
        # to the caller method, this should be used for url reconstruction
        # note that the state value is also returned and should be store in
        # the current handling connection so that it may latter be used
        return resolved

    def balancer(self, values):
        is_sequence = type(values) in (list, tuple)
        if not is_sequence: return values, None
        return self.balancer_m(values)

    def balancer_robin(self, values):
        index = self.robin.get(values, 0)
        prefix = values[index]
        next = 0 if index + 1 == len(values) else index + 1
        self.robin[values] = next
        return prefix, None

    def balancer_smart(self, values):
        queue = self.smart.get(values, None)
        if not queue:
            queue = netius.common.PriorityDict()
            for value in values: queue[value] = [0, 0]
            self.smart[values] = queue

        prefix = queue.smallest()

        return prefix, (prefix, queue)

    def acquirer(self, state):
        self.acquirer_m(state)

    def acquirer_robin(self, state):
        pass

    def acquirer_smart(self, state):
        if not state: return
        prefix, queue = state
        sorter = queue[prefix]
        sorter[0] += 1
        queue[prefix] = sorter

    def releaser(self, state):
        self.releaser_m(state)

    def releaser_robin(self, state):
        pass

    def releaser_smart(self, state):
        if not state: return
        prefix, queue = state
        sorter = queue[prefix]
        sorter[0] -= 1
        if sorter[0] == 0: sorter[1] = time.time() * -1
        queue[prefix] = sorter

    def _on_prx_message(self, client, parser, message):
        _connection = parser.owner
        busy = _connection.busy if hasattr(_connection, "busy") else 0
        state = _connection.state if hasattr(_connection, "state") else None
        if busy: self.busy_conn -= 1; _connection.busy -= 1
        if state: self.releaser(state); _connection.state = None
        netius.servers.ProxyServer._on_prx_message(self, client, parser, message)

    def _on_prx_close(self, client, _connection):
        busy = _connection.busy if hasattr(_connection, "busy") else 0
        state = _connection.state if hasattr(_connection, "state") else None
        if busy: self.busy_conn -= busy; _connection.busy -= busy
        if state: self.releaser(state); _connection.state = None
        netius.servers.ProxyServer._on_prx_close(self, client, _connection)

if __name__ == "__main__":
    import logging
    regex = (
        (re.compile(r"https://host\.com"), "http://localhost"),
        (re.compile(r"https://([a-zA-Z]*)\.host\.com"), "http://localhost/{0}")
    )
    hosts = {
        "host.com" : "http://localhost"
    }
    auth = {
        "host.com" : netius.PasswdAuth("extras/htpasswd")
    }
    redirect = {
        "host.com" : "other.host.com"
    }
    server = ReverseProxyServer(
        regex = regex,
        hosts = hosts,
        auth = auth,
        redirect = redirect,
        level = logging.INFO
    )
    server.serve(env = True)
<commit_msg>new simple based resolution<commit_after>#!/usr/bin/python
# -*- coding: utf-8 -*-

# Hive Netius System
# Copyright (c) 2008-2016 Hive Solutions Lda.
#
# This file is part of Hive Netius System.
#
# Hive Netius System is free software: you can redistribute it and/or modify
# it under the terms of the Apache License as published by the Apache
# Foundation, either version 2.0 of the License, or (at your option) any
# later version.
#
# Hive Netius System is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# Apache License for more details.
#
# You should have received a copy of the Apache License along with
# Hive Netius System. If not, see <http://www.apache.org/licenses/>.

__author__ = "João Magalhães <joamag@hive.pt>"
""" The author(s) of the module """

__version__ = "1.0.0"
""" The version of the module """

__revision__ = "$LastChangedRevision$"
""" The revision number of the module """

__date__ = "$LastChangedDate$"
""" The last change date of the module """

__copyright__ = "Copyright (c) 2008-2016 Hive Solutions Lda."
""" The copyright for the module """

__license__ = "Apache License, Version 2.0"
""" The license for the module """

import re
import time

import netius.common
import netius.servers

class ReverseProxyServer(netius.servers.ProxyServer):
    """
    Reverse HTTP proxy implementation based on the more generalized
    infra-structure of proxy server.

    Supports multiple scheduling strategies, being the "smart" one
    the most efficient for most of the possible scenarios.

    Conditional re-usage of connections based on boolean flag is possible,
    but care should be taken when using re-usage so that no multiple
    rules are applied for the same connection (eg: https://host.com/hive,
    https://host.com/colony as different rules), this would pose serious
    problems if the back-end servers are different for each rule or if
    the way the final back-end url is created is different for each rule.
    """

    def __init__(
        self,
        config = "proxy.json",
        regex = {},
        hosts = {},
        auth = {},
        redirect = {},
        strategy = "smart",
        reuse = True,
        *args,
        **kwargs
    ):
        netius.servers.ProxyServer.__init__(self, *args, **kwargs)
        if type(regex) == dict: regex = regex.items()
        if not type(hosts) == dict: hosts = dict(hosts)
        self.load_config(
            path = config,
            regex = regex,
            hosts = hosts,
            auth = auth,
            redirect = redirect,
            strategy = strategy,
            reuse = reuse,
            robin = dict(),
            smart = netius.common.PriorityDict()
        )
        self.busy_conn = 0
        self.balancer_m = getattr(self, "balancer_" + self.strategy)
        self.acquirer_m = getattr(self, "acquirer_" + self.strategy)
        self.releaser_m = getattr(self, "releaser_" + self.strategy)

    def info_dict(self, full = False):
        info = netius.servers.ProxyServer.info_dict(self, full = full)
        info.update(
            reuse = self.reuse,
            strategy = self.strategy,
            busy_conn = self.busy_conn
        )
        return info

    def on_headers(self, connection, parser):
        netius.servers.ProxyServer.on_headers(self, connection, parser)

        # retrieves the various parts/configuration values of the parser, that
        # are going to be used in the processing/routing of the proxy request,
        # note that some of these values need to be transformed to be used
        method = parser.method.upper()
        path = parser.path_s
        headers = parser.headers
        version_s = parser.version_s
        is_secure = connection.ssl
        host = headers.get("host", None)
        address = headers.get("x-forwarded-for", connection.address[0])
        protocol = headers.get("x-forwarded-proto", None)
        protocol = protocol or ("https" if is_secure else "http")

        # tries to determine if a proper (client side) redirection should operation
        # should be applied to the current request, if that's the case (match) an
        # immediate response is returned with proper redirection instructions
        redirect = self.redirect.get(host, None)
        if redirect:
            location = "%s://%s%s" % (protocol, redirect, path)
            connection.send_response(
                headers = dict(
                    location = location
                ),
                version = version_s,
                code = 303,
                code_s = "See Other",
                apply = True
            )
            return

        # tries to extract the various attributes of the current connection
        # that are going to be used for the routing operation, this attributes
        # should avoid a new rule setting operation (expensive) and provide
        # connection, prefix and state re-usage whenever possible, note that
        # under some situation the prefix may change for the same connection
        # and so the connection re-usage is not possible
        prefix = connection.prefix if hasattr(connection, "prefix") else None
        state = connection.state if hasattr(connection, "state") else None
        reusable = hasattr(connection, "proxy_c")

        # constructs the url that is going to be used by the rule engine and
        # then "forwards" both the url and the parser object to the rule engine
        # in order to obtain the possible prefix value for url reconstruction,
        # a state value is also retrieved, this value will be used latter for
        # the acquiring and releasing parts of the balancing strategy operation
        url = "%s://%s%s" % (protocol, host, path)
        if not self.reuse or not reusable: prefix, state = self.rules(url, parser)

        # in case no prefix is defined at this stage there's no matching
        # against the currently defined rules and so an error must be raised
        # and propagated to the client connection (end user notification)
        if not prefix:
            self.debug("No valid proxy endpoint found")
            connection.send_response(
                data = "No valid proxy endpoint found",
                headers = dict(
                    connection = "close"
                ),
                version = version_s,
                code = 404,
                code_s = "Not Found",
                apply = True,
                callback = self._prx_close
            )
            return

        # verifies if the current host requires come kind of special authorization
        # process using the default basic http authorization process
        auth = self.auth.get(host.split(":", 1)[0], None)
        if auth:
            # tries to retrieve the authorization header for the current request
            # and in case it's not defined "raises" a non authorized response
            authorization = headers.get("authorization", None)
            if not authorization:
                connection.send_response(
                    data = "Not authorized",
                    headers = {
                        "connection" : "close",
                        "wWW-authenticate" : "Basic realm=\"insert realm\""
                    },
                    version = version_s,
                    code = 401,
                    code_s = "Not Authorized",
                    apply = True,
                    callback = self._prx_close
                )
                return

            # runs the authorization process for the requested auth value (file path)
            # and using the current parser for the connection handling, this should
            # return a valid response in case the authorization is valid and an invalid
            # value otherwise, in case there's a failure a not authorized value is returned
            result = self.authorize(auth, parser)
            if not result:
                connection.send_response(
                    data = "Not authorized",
                    headers = {
                        "connection" : "close",
                        "wWW-authenticate" : "Basic realm=\"insert realm\""
                    },
                    version = version_s,
                    code = 401,
                    code_s = "Not Authorized",
                    apply = True,
                    callback = self._prx_close
                )
                return

        # runs the acquire operation for the current state, this should
        # update the current scheduling algorithm internal structures so
        # that they properly handle the new handle operation, an inverse
        # release operation should be performed at the end of the handling
        self.acquirer(state)

        # re-calculates the url for the reverse connection based on the
        # prefix value that has just been "resolved" using the rule engine
        # this value should be constructed based on the original path
        url = prefix + path

        # updates the various headers that are related with the reverse
        # proxy operation this is required so that the request gets
        # properly handled by the reverse server, otherwise some problems
        # would occur in the operation of handling the request
        headers["x-forwarded-for"] = address
        headers["x-forwarded-proto"] = protocol

        # verifies if the current connection already contains a valid
        # a proxy connection if that's the case that must be unset from
        # the connection and from the connection map internal structures
        # at least until the http client returns from the method call
        proxy_c = hasattr(connection, "proxy_c") and connection.proxy_c
        proxy_c = proxy_c or None
        connection.proxy_c = None
        if proxy_c in self.conn_map: del self.conn_map[proxy_c]

        # calls the proper (http) method in the client this should acquire
        # a new connection and start the process of sending the request
        # to the associated http server (request handling)
        _connection = self.http_client.method(
            method,
            url,
            headers = headers,
            version = version_s,
            encodings = None,
            connection = proxy_c
        )

        # sets the state attribute in the connection so that it's possible
        # to retrieve it latter for tagging evaluation, this is required for
        # advanced load balancing techniques to be performed
        _connection.state = state

        # sets the current connection as busy, as it's waiting for a message
        # to be returned from the the back-end side, then increments the number
        # of currently busy connections (as expected)
        _connection.busy = _connection.busy if hasattr(_connection, "busy") else 0
        _connection.busy += 1
        self.busy_conn += 1

        # prints a debug message about the connection becoming a waiting
        # connection meaning that the connection with the client host has
        # not been yet established (no data has been  received)
        self.debug("Setting connection as waiting, proxy connection loading ...")

        # sets the current http back-end client connection as waiting and then
        # maps it as the proxy connection in the connection and also creates
        # the reverse mapping using the connection map of the current server
        _connection.waiting = True
        connection.proxy_c = _connection
        connection.prefix = prefix
        connection.state = state
        self.conn_map[_connection] = connection

    def rules(self, url, parser):
        resolved = self.rules_regex(url, parser)
        if resolved[0]: return resolved
        resolved = self.rules_host(url, parser)
        if resolved[0]: return resolved
        return None, None

    def rules_regex(self, url, parser):
        # sets the prefix and state values initially for the invalid/unset value,
        # these values are going to be populated once a valid match is found
        prefix = None
        state = None

        # iterates over the complete set of defined regex values to try
        # to find a valid match and apply the groups value for format
        # if the complete chain of regex is processed but there's no
        # valid match the prefix value is considered to be unset
        for regex, _prefix in self.regex:
            match = regex.match(url)
            if not match: continue
            _prefix, _state = self.balancer(_prefix)
            groups = match.groups()
            if groups: _prefix = _prefix.format(*groups)
            prefix = _prefix
            state = _state
            break

        # returns the prefix and state values that have just been resolved
        # through regex based validation, this value may be unset for a mismatch
        return prefix, state

    def rules_host(self, url, parser):
        # retrieves the reference to the headers map from the parser so
        # that it may be used to retrieve the current host value and try
        # to match it against any of the currently defined rules
        headers = parser.headers

        # retrieves the host header from the received set of headers,
        # removing the port definition part of the host and then verifies
        # the complete set of defined hosts in order to check for the
        # presence of such rule, in case there's a match the defined
        # url prefix is going to be used instead, the balancer operation
        # is then used to "resolve" the final prefix value from sequence
        host = headers.get("host", None)
        host_s = host.split(":", 1)[0] if host else host
        prefix = self.hosts.get(host_s, None)
        prefix = self.hosts.get(host, prefix)
        resolved = self.balancer(prefix)

        # returns the final "resolved" prefix value (in case there's any)
        # to the caller method, this should be used for url reconstruction
        # note that the state value is also returned and should be store in
        # the current handling connection so that it may latter be used
        return resolved

    def balancer(self, values):
        is_sequence = type(values) in (list, tuple)
        if not is_sequence: return values, None
        return self.balancer_m(values)

    def balancer_robin(self, values):
        index = self.robin.get(values, 0)
        prefix = values[index]
        next = 0 if index + 1 == len(values) else index + 1
        self.robin[values] = next
        return prefix, None

    def balancer_smart(self, values):
        queue = self.smart.get(values, None)
        if not queue:
            queue = netius.common.PriorityDict()
            for value in values: queue[value] = [0, 0]
            self.smart[values] = queue

        prefix = queue.smallest()

        return prefix, (prefix, queue)

    def acquirer(self, state):
        self.acquirer_m(state)

    def acquirer_robin(self, state):
        pass

    def acquirer_smart(self, state):
        if not state: return
        prefix, queue = state
        sorter = queue[prefix]
        sorter[0] += 1
        queue[prefix] = sorter

    def releaser(self, state):
        self.releaser_m(state)

    def releaser_robin(self, state):
        pass

    def releaser_smart(self, state):
        if not state: return
        prefix, queue = state
        sorter = queue[prefix]
        sorter[0] -= 1
        if sorter[0] == 0: sorter[1] = time.time() * -1
        queue[prefix] = sorter

    def _on_prx_message(self, client, parser, message):
        _connection = parser.owner
        busy = _connection.busy if hasattr(_connection, "busy") else 0
        state = _connection.state if hasattr(_connection, "state") else None
        if busy: self.busy_conn -= 1; _connection.busy -= 1
        if state: self.releaser(state); _connection.state = None
        netius.servers.ProxyServer._on_prx_message(self, client, parser, message)

    def _on_prx_close(self, client, _connection):
        busy = _connection.busy if hasattr(_connection, "busy") else 0
        state = _connection.state if hasattr(_connection, "state") else None
        if busy: self.busy_conn -= busy; _connection.busy -= busy
        if state: self.releaser(state); _connection.state = None
        netius.servers.ProxyServer._on_prx_close(self, client, _connection)

if __name__ == "__main__":
    import logging
    hosts = {
        "host.com:9090" : "http://cnn.com"
    }
    server = ReverseProxyServer(
        hosts = hosts,
        level = logging.INFO
    )
    server.serve(env = True)
<|endoftext|>


<commit_before>from datetime import timedelta

from django.db import models
from django.conf import settings
from django.utils import timezone
from django.contrib.auth.models import User
from django.contrib.sites.models import Site

class UserProfile(models.Model):
    pass

class Status(models.Model):
    """A Story's current state in workflow."""

    name = models.CharField(max_length=32, unique=True)
    position = models.PositiveSmallIntegerField(unique=True)

    class Meta:
        verbose_name_plural = "Statuses"
        ordering = ['position']

    def __unicode__(self):
        return self.name

class Story(models.Model):
    """A standalone piece of content that conveys a message to a consumer.

    Stories can include or be composed entirely of text, an Image, a Video, an
    Audio piece, a Poll, etc. or any combination of the aforementioned.
    """

    # primary content
    assignment_slug = models.CharField(max_length=64,
                                       help_text='Succinct, quasi-unique '
                                       'identifier for this story. Should '
                                       'NOT contain a date, section, or any '
                                       'other information already stored on '
                                       'another story field.')
    status = models.ForeignKey('Status',
                               help_text='Current state in workflow.')
    title = models.CharField(max_length=128, blank=True, help_text='Publicly '
                            'displayed headline.')
    url_slug = models.SlugField(max_length=128, blank=True,
                                help_text='Forms a part of the URL for this '
                                          'story.')
    authors = models.ManyToManyField('authors.Author',
                                     related_name='core_story_authors',
                                     blank=True)
    teaser = models.CharField(max_length=128, blank=True, help_text='Short '
                              'subtext related to the story to encourage '
                              'readers to read the story.')
    subhead = models.CharField(max_length=128, blank=True,
                               help_text='Addtional title that suplements '
                                         'the primary title.')
    body = models.TextField(blank=True)
    alternate_template = models.ForeignKey('display.Template', blank=True,
                                           null=True, help_text='Optional '
                                           'alternate template to use for '
                                           'this story.')

    # planning
    summary = models.TextField(blank=True)
    angle = models.TextField(blank=True)
    sources = models.TextField(blank=True)
    late_run = models.BooleanField(default=False, help_text='Whether or '
                                   'not the story is planned to come in '
                                   'later than stories in the section '
                                   'typically do.')

    # organization
    position = models.PositiveIntegerField(unique=True, db_index=True,
                                           help_text='How this story is '
                                           'ordered relative to other '
                                           'stories.')
    sections = models.ManyToManyField('organization.Section', blank=True)
    tags = models.ManyToManyField('organization.Tag', blank=True)
    sites = models.ManyToManyField(Site)

    # card
    card = models.ForeignKey('attachments.Image', null=True, blank=True,
                             related_name='core_story_card',
                             help_text='Image to display with this story in '
                                       'a story list view.')
    card_size = models.ForeignKey('display.CardSize', help_text='The aspect '
                                  'ratio in which the card should be '
                                  'displayed.')
    card_focus = models.CharField(max_length=2,
                                  choices=settings.IMAGE_FOCUS_CHOICES,
                                  default='cc', help_text='Location of the '
                                  'focal point of the card image.')
    feature_card_image = models.BooleanField(default=True,
                                             help_text='Whether or not the '
                                             'card image should also be '
                                             'displayed as the story\'s '
                                             'featured image.')

    # dates and times
    publish_time = models.DateTimeField()
    breaking_duration = models.PositiveSmallIntegerField(default=0,
                                                         help_text='How long '
                                                         'the story should be '
                                                         'displayed as '
                                                         'breaking news.')
    created = models.DateTimeField(auto_now_add=True)
    last_updated = models.DateTimeField(auto_now=True)

    # linked media
    featured_image = models.ForeignKey('attachments.Image', null=True,
                                       blank=True,
                                     related_name='core_story_featured_image',
                                       help_text='The most prominent image '
                                       'associated with this story.')
    featured_video = models.ForeignKey('attachments.Video', null=True,
                                       blank=True, help_text='The most '
                                       'prominent video associated with this '
                                       'story.')
    featured_audio = models.ForeignKey('attachments.Audio', null=True,
                                       blank=True, help_text='The most '
                                       'prominent audio associated with this '
                                       'story.')
    review = models.ForeignKey('attachments.Review', null=True, blank=True)
    poll = models.ForeignKey('attachments.Poll', null=True, blank=True)
    game = models.ForeignKey('sports.Game', null=True, blank=True,
                             help_text='Sports game associated with this '
                                       'story.')
    # social_media_post = models.OneToOneField('scheduler.SMPost', null=True,
    #                                          blank=True)

    class Meta:
        verbose_name_plural = "Stories"
        ordering = ['-position']

    def get_featured_image(self):
        """Custom accessor for featured image.

        Needed because the `feature_card_image` field determines whether the
        image stored in the `card` field or the image stored in
        `featured_image` field should be displayed as the 'featured image'
        for an article.
        """

        if self.card and self.feature_card_image:
            return self.card
        else:
            return self.featured_image

    def is_published(self):
        """Returns True if the article's status is 'Ready to publish' and the
        current time is greater than (later than) the publish time.
        """

        return (self.status == Status.objects.last() and
                self.publish_time < timezone.now())

    def is_breaking(self):
        """Whether or not the article is currently breaking news"""

        return (self.publish_time + timedelta(hours=self.breaking_duration) >
                timezone.now())

    def get_path(self):
        """Get the URL path to the article from the website root."""

        return "/%s/%s/" % (self.publish_time.strftime("%Y/%m/%d"),
                            self.url_slug)

    def save(self, *args, **kwargs):
        if self.position is None:
            first_story = Story.objects.first()
            if first_story:
                self.position = first_story.position + 1
            else:
                self.position = 0
        super(Story, self).save(*args, **kwargs)

    def __unicode__(self):
        return self.assignment_slug

class Page(models.Model):
    """A single web page which is completely unrelated to a Story.

    Examples include an about page, a staff page, and an opinion submission
    page.
    """

    parent = models.ForeignKey('self', null=True, blank=True)
    title = models.CharField(max_length=128, blank=True)
    slug = models.SlugField(max_length=128, help_text='Forms part of the '
                                                      'URL for this story.')
    alternate_template = models.ForeignKey('display.Template', null=True,
                                           blank=True, help_text='Optional '
                                           'alternate template to use for '
                                           'this story.')
    body = models.TextField(blank=True)
    site = models.ForeignKey(Site)

    class Meta:
        unique_together = ('parent', 'slug')

    def __unicode__(self):
        return self.title
<commit_msg>validate that a Story's publish date and url slug are unique. closes #2<commit_after>from datetime import timedelta

from django.db import models
from django.conf import settings
from django.utils import timezone
from django.core.exceptions import ValidationError, NON_FIELD_ERRORS
from django.contrib.auth.models import User
from django.contrib.sites.models import Site

class UserProfile(models.Model):
    pass

class Status(models.Model):
    """A Story's current state in workflow."""

    name = models.CharField(max_length=32, unique=True)
    position = models.PositiveSmallIntegerField(unique=True)

    class Meta:
        verbose_name_plural = "Statuses"
        ordering = ['position']

    def __unicode__(self):
        return self.name

class Story(models.Model):
    """A standalone piece of content that conveys a message to a consumer.

    Stories can include or be composed entirely of text, an Image, a Video, an
    Audio piece, a Poll, etc. or any combination of the aforementioned.
    """

    # primary content
    assignment_slug = models.CharField(max_length=64,
                                       help_text='Succinct, quasi-unique '
                                       'identifier for this story. Should '
                                       'NOT contain a date, section, or any '
                                       'other information already stored on '
                                       'another story field.')
    status = models.ForeignKey('Status',
                               help_text='Current state in workflow.')
    title = models.CharField(max_length=128, blank=True, help_text='Publicly '
                            'displayed headline.')
    url_slug = models.SlugField(max_length=128, blank=True,
                                help_text='Forms a part of the URL for this '
                                          'story.')
    authors = models.ManyToManyField('authors.Author',
                                     related_name='core_story_authors',
                                     blank=True)
    teaser = models.CharField(max_length=128, blank=True, help_text='Short '
                              'subtext related to the story to encourage '
                              'readers to read the story.')
    subhead = models.CharField(max_length=128, blank=True,
                               help_text='Addtional title that suplements '
                                         'the primary title.')
    body = models.TextField(blank=True)
    alternate_template = models.ForeignKey('display.Template', blank=True,
                                           null=True, help_text='Optional '
                                           'alternate template to use for '
                                           'this story.')

    # planning
    summary = models.TextField(blank=True)
    angle = models.TextField(blank=True)
    sources = models.TextField(blank=True)
    late_run = models.BooleanField(default=False, help_text='Whether or '
                                   'not the story is planned to come in '
                                   'later than stories in the section '
                                   'typically do.')

    # organization
    position = models.PositiveIntegerField(unique=True, db_index=True,
                                           help_text='How this story is '
                                           'ordered relative to other '
                                           'stories.')
    sections = models.ManyToManyField('organization.Section', blank=True)
    tags = models.ManyToManyField('organization.Tag', blank=True)
    sites = models.ManyToManyField(Site)

    # card
    card = models.ForeignKey('attachments.Image', null=True, blank=True,
                             related_name='core_story_card',
                             help_text='Image to display with this story in '
                                       'a story list view.')
    card_size = models.ForeignKey('display.CardSize', help_text='The aspect '
                                  'ratio in which the card should be '
                                  'displayed.')
    card_focus = models.CharField(max_length=2,
                                  choices=settings.IMAGE_FOCUS_CHOICES,
                                  default='cc', help_text='Location of the '
                                  'focal point of the card image.')
    feature_card_image = models.BooleanField(default=True,
                                             help_text='Whether or not the '
                                             'card image should also be '
                                             'displayed as the story\'s '
                                             'featured image.')

    # dates and times
    publish_time = models.DateTimeField()
    breaking_duration = models.PositiveSmallIntegerField(default=0,
                                                         help_text='How long '
                                                         'the story should be '
                                                         'displayed as '
                                                         'breaking news.')
    created = models.DateTimeField(auto_now_add=True)
    last_updated = models.DateTimeField(auto_now=True)

    # linked media
    featured_image = models.ForeignKey('attachments.Image', null=True,
                                       blank=True,
                                     related_name='core_story_featured_image',
                                       help_text='The most prominent image '
                                       'associated with this story.')
    featured_video = models.ForeignKey('attachments.Video', null=True,
                                       blank=True, help_text='The most '
                                       'prominent video associated with this '
                                       'story.')
    featured_audio = models.ForeignKey('attachments.Audio', null=True,
                                       blank=True, help_text='The most '
                                       'prominent audio associated with this '
                                       'story.')
    review = models.ForeignKey('attachments.Review', null=True, blank=True)
    poll = models.ForeignKey('attachments.Poll', null=True, blank=True)
    game = models.ForeignKey('sports.Game', null=True, blank=True,
                             help_text='Sports game associated with this '
                                       'story.')
    # social_media_post = models.OneToOneField('scheduler.SMPost', null=True,
    #                                          blank=True)

    class Meta:
        verbose_name_plural = "Stories"
        ordering = ['-position']

    def get_featured_image(self):
        """Custom accessor for featured image.

        Needed because the `feature_card_image` field determines whether the
        image stored in the `card` field or the image stored in
        `featured_image` field should be displayed as the 'featured image'
        for an article.
        """

        if self.card and self.feature_card_image:
            return self.card
        else:
            return self.featured_image

    def is_published(self):
        """Returns True if the article's status is 'Ready to publish' and the
        current time is greater than (later than) the publish time.
        """

        return (self.status == Status.objects.last() and
                self.publish_time < timezone.now())

    def is_breaking(self):
        """Whether or not the article is currently breaking news"""

        return (self.publish_time + timedelta(hours=self.breaking_duration) >
                timezone.now())

    def get_path(self):
        """Get the URL path to the article from the website root."""

        return "/%s/%s/" % (self.publish_time.strftime("%Y/%m/%d"),
                            self.url_slug)

    def validate_unique(self, *args, **kwargs):
        super(Story, self).validate_unique(*args, **kwargs)

        # validate that this story does not have the same publish date
        # (publish_time field) and url slug as any other story. this constraint
        # is necessary because story URL patterns are anticipated to follow
        # the format: /year/month/day/story-url-slug, and the above case would
        # result in multiple stories mapping to the same URL.
        stories_on_same_day = self.__class__.objects.filter(
                                   publish_time__year=self.publish_time.year,
                                   publish_time__month=self.publish_time.month,
                                   publish_time__day=self.publish_time.day
        ).exclude(pk=self.pk)
        for story in stories_on_same_day:
            if story.url_slug == self.url_slug:
                raise ValidationError({
                        NON_FIELD_ERRORS: [
                            'Two stories with the same url_slug cannot be'\
                            'set to publish on the same day.'
                        ]
                    })

    def save(self, *args, **kwargs):
        if self.position is None:
            first_story = Story.objects.first()
            if first_story:
                self.position = first_story.position + 1
            else:
                self.position = 0
        super(Story, self).save(*args, **kwargs)

    def __unicode__(self):
        return self.assignment_slug

class Page(models.Model):
    """A single web page which is completely unrelated to a Story.

    Examples include an about page, a staff page, and an opinion submission
    page.
    """

    parent = models.ForeignKey('self', null=True, blank=True)
    title = models.CharField(max_length=128, blank=True)
    slug = models.SlugField(max_length=128, help_text='Forms part of the '
                                                      'URL for this story.')
    alternate_template = models.ForeignKey('display.Template', null=True,
                                           blank=True, help_text='Optional '
                                           'alternate template to use for '
                                           'this story.')
    body = models.TextField(blank=True)
    site = models.ForeignKey(Site)

    class Meta:
        unique_together = ('parent', 'slug')

    def __unicode__(self):
        return self.title
<|endoftext|>