#StackBounty: #python #html #python-3.x #pandas DataFrame – table in table from nested dictionary

Bounty: 50

I use python 3.

This is my data structure:

dictionary = {
    'HexaPlex x50': {
        'Vendor': 'Dell  Inc.',
        'BIOS Version': '12.72.9',
        'Newest BIOS': '12.73.9',
        'Against M & S': 'Yes',
        'W10 Support': 'Yes',
        'Computers': {
            'someName001': '12.72.9',
            'someName002': '12.73.9',
            'someName003': '12.73.9'
        },
        'Mapped Category': ['SomeOtherCategory']
    },
    ...
}

I have managed to create a table that displays columns created from keys of the first nested dictionary (which starts with 'Vendor'). The row name is 'HexaPlex x50'. One of the columns contains computers with a number, i.e. the nested dictionary:

{'someName001': '12.72.9',
 'someName002': '12.73.9',
 'someName003': '12.73.9'}

I would like to be able to have the key values pairs inside the table in the cell under column 'Computers', in effect a nested table.

ATM it looks like this:

Screenshot of current table display

The table should look somewhat like this

Screenshot of preferred table with one dictionary entry per row

How can I achieve this?

Further, I would like to color the numbers or the cell that has a lower BIOS version than the newest one.

I also face the problem that in one case the dictionary that contains the computers is so large that it gets abbreviated even though I have set pd.set_option('display.max_colwidth', -1). This looks like so:

Close-up of dictionary string


Get this bounty!!!

#StackBounty: #python #python-3.x #functional-programming #meta-programming Decorate instance method with (arbitrary) meta data in python

Bounty: 50

Requirements

What I need: attach meta information to methods, such that

  1. it is ‘easy’ to retrieve all such methods for a given class instance
  2. methods can still be called ‘in a normal way’, e.g. obj.method()
  3. meta-data is accessible from the decorated method, e.g. obj.method.data
  4. IDEs (PyCharm in particular) do not produce any warnings or errors (and, if possible, IDE support, e.g. auto-completion, should be able to handle the annotation)

Additionally, I would like the code to be readable/intuitive (not necessarily the super classes, though), generally robust and ‘bug-free’. I accept the limitation that my decorators need to be the most ‘outer’ decorator for the automatic collection to take place.

From my point of view, overcoming function/method transformation while still exposing an arbitrary object type (not a function type — thinking of this, maybe subclassing a FunctionType might be another idea?) is the hardest challenge.

What do you think of the following three solutions? Is there something I did miss?

Code

class MethodDecoratorWithIfInCall(object):
    def __init__(self):
        self._func = None

    def __call__(self, *args, **kwargs):
        if self._func is None:
            assert len(args) == 1 and len(kwargs) == 0
            self._func = args[0]
            return self
        else:
            return self._func(*args, **kwargs)

    def __get__(self, *args, **kwargs):
        # update func reference to method object
        self._func = self._func.__get__(*args, **kwargs)
        return self


class MacroWithIfInCall(MethodDecoratorWithIfInCall):
    def __init__(self, name):
        super(MacroWithIfInCall, self).__init__()
        self.name = name


class MethodDecoratorWithExplicitDecorate(object):
    def __init__(self, *args, **kwargs):
        # wildcard parameters to satisfy PyCharm
        self._func = None

    def __call__(self, *args, **kwargs):
        return self._func(*args, **kwargs)

    def __get__(self, *args, **kwargs):
        # update func reference to method object
        self._func = self._func.__get__(*args, **kwargs)
        return self

    def _decorate(self):
        def _set_func(func):
            self._func = func
            return self
        return _set_func

    @classmethod
    def decorate(cls, *args, **kwargs):
        obj = cls(*args, **kwargs)
        return obj._decorate()


class MacroWithExplicitDecorate(MethodDecoratorWithExplicitDecorate):
    def __init__(self, name):
        super(MacroWithExplicitDecorate, self).__init__()
        self.name = name


class MacroWithoutSuperclass(object):
    def __init__(self, func, name):
        self.func = func
        self.name = name

    def __get__(self, *args, **kwargs):
        # update func reference to method object
        self.func = self.func.__get__(*args, **kwargs)
        return self

    def __call__(self, *args, **kwargs):
        return self.func(*args, **kwargs)

    @staticmethod
    def decorate(name):
        return lambda func: MacroWithoutSuperclass(func, name)


class Shell:
    def __init__(self):
        macros = [macro for macro in map(self.__getattribute__, dir(self))
                  if isinstance(macro, (MacroWithIfInCall, MacroWithExplicitDecorate, MacroWithoutSuperclass))]

        for macro in macros:
            print(macro.name, macro())

    @MacroWithIfInCall(name="macro-with-if-in-call")
    def macro_add_1(self):
        return "called"

    @MacroWithExplicitDecorate.decorate(name="macro-with-explicit-decorate")
    def macro_add_2(self):
        return "called"

    @MacroWithoutSuperclass.decorate(name="macro-without-superclass")
    def macro_add_3(self):
        return "called"


if __name__ == '__main__':
    shell = Shell()

Output

macro-with-if-in-call called
macro-with-explicit-decorate called
macro-without-superclass called

Disclaimer

This code is/will be part of a public, GPL-3.0 repository.


Get this bounty!!!

#StackBounty: #python #django #python-3.x #django-models Django 2.0 Access Models (CREATE/REMOVE/FILTER) Standalone [without manage.py …

Bounty: 50

I have a Django project and I wanted to generate some objects (from the models)

What I’m trying to get at : Standalone Python Script to create bunch of objects and/or filter,delete.

after importing the model with from apps.base.models import MyModel
and setting up the configuration as the previous StackOverflow Questions suggested I was not able to run the script.

import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myProject.settings")
import django

django.setup()
from apps.base.models import MyModel

Please note that this is on Django version 2.0.6 [Django 2.0+].

Correct settings have been used, (i.e. myProject.settings)

  • After properly configuring everything else I get the following error:
    • RuntimeError: Model class apps.base.models.MyModel doesn't declare an explicit app_label and isn't in an application in INSTALLED_APPS.


Get this bounty!!!

#StackBounty: #python #python-3.x #setuptools #directory-structure Elegant way to refer to files in data science project

Bounty: 50

I have spent few recent days to learn how to structure data science project to keep it simple, reusable and pythonic. Sticking to this guideline I have created my_project. You can see it’s structure below.

├── README.md          
├── data
│   ├── processed          <-- data files
│   └── raw                            
├── notebooks  
|   └── notebook_1                             
├── setup.py              
|
├── settings.py            <-- settings file   
└── src                
    ├── __init__.py    
    │
    └── data           
        └── get_data.py    <-- script  

I defined a function that loads data from .data/processed. I want to use this function in other scripts and also in jupyter notebooks located in .notebooks.

def data_sample(code=None):
    df = pd.read_parquet('../../data/processed/my_data')
    if not code:
        code = random.choice(df.code.unique())
    df = df[df.code == code].sort_values('Date')
    return df

Obviously this function won’t work anywhere unless I run it directly in the script where it is defined.
My idea was to create settings.py where I’d declare:

from os.path import join, dirname

DATA_DIR = join(dirname(__file__), 'data', 'processed')

So now I can write:

from my_project import settings
import os

def data_sample(code=None):
    file_path = os.path.join(settings.DATA_DIR, 'my_data')
    df = pd.read_parquet(file_path)
    if not code:
        code = random.choice(df.code.unique())
    df = df[df.code == code].sort_values('Date')
    return df

Questions:

  1. Is this common practice to refer to files in this way? settings.DATA_DIR looks kinda ugly.

  2. Is this at all how settings.py should be used? And should it be placed in this directory? I have seen it in different spot in this repo under .samr/settings.py

I understand that there might not be ‘one right answer’, I just try to find logical, elegant way of handling these things.


Get this bounty!!!

#StackBounty: #python #beginner #python-3.x Discord.py bot reading mariaDB and creating embeds

Bounty: 100

My first real Python project. I have a web scraping PowerShell utility that scrapes Kijiji (Canadian Craigslist) for listings and loads them into MariaDB. What the bot does is periodically check the database for listing flagged as new. Creates embeds from the listing data and post them to an appropriate discord channel (also removes the new flag). I really like the import system so I tried to break up the project into logical chunks. The bot settings are driven by JSON. A few bot commands were added in here as well.

This is the main py file. It is expecting a config file called bot_cfg.json to be in the same directory from where it was executed. For me that is fine. It would be nicer to accept the path as input but I prefer the assumption for now.

kjiji-bot.py

# Kijiji Bot
# Uses Python 3.6.5

# Discord specific imports
import discord
from discord.ext import commands
from discord.ext.commands import Bot
# Miscellaneous imports
import asyncio
import logging
import os
from pathlib import Path
from random import randint
from datetime import datetime
# Database
from sqlalchemy import create_engine, and_
from sqlalchemy.orm import Session
from sqlalchemy.orm.exc import NoResultFound
# Custom Class
from listing import Listing, Base
from botconfig import BotConfig

# Set up Discord logging
logger = logging.getLogger('discord')
logger.setLevel(logging.DEBUG)
handler = logging.FileHandler(filename='discord.log', encoding='utf-8', mode='w')
handler.setFormatter(logging.Formatter('%(asctime)s:%(levelname)s:%(name)s: %(message)s'))
logger.addHandler(handler)

# Configuration file name. To be located in same directory as script
config_file_name = "bot_cfg.json"

class KijijiListing(object):
    '''The basic Kijiji Listing information'''

    def __init__(self, dictionary):
        self.url = dictionary['absoluteurl']
        self.imageurl = dictionary['imageurl']
        self.id = dictionary['id']
        self.posted = dictionary['postedasdate']
        self.title = dictionary['title']
        self.description = dictionary['shortdescription']
        self.location = dictionary['location']
        self.price = dictionary['price']
        self.thumbnail = 'https://www.shareicon.net/data/128x128/2016/08/18/810389_strategy_512x512.png'

    def __str__(self):
        return 'Title: {}nDescription: {}nPrice: {}nURL: {}'.format(
            self.title, self.description, self.price, self.url
        )

    def to_embed(self):
        '''Created a discord embed from this instances properties'''
        listing_embed = discord.Embed(
            title=self.title, description=self.description, color=discord.Colour(randint(0, 16777215)),
            url=self.url)
        listing_embed.add_field(name='Location', value=self.location, inline=True)
        listing_embed.add_field(name='Price', value=self.price, inline=True)
        listing_embed.set_image(url=self.imageurl)
        listing_embed.set_thumbnail(
            url=self.thumbnail)
        listing_embed.set_footer(text='Listed: {}'.format(self.posted))
        return listing_embed

# Scripts running location. Only set if called via python.exe
__location__ = os.path.realpath(
    # From https://docs.python.org/3/library/os.path.html
    # If a component is an absolute path, all previous components
    # are thrown away and joining continues from the absolute path component.
    os.path.join(os.getcwd(), os.path.dirname(__file__)))

# Load Configuration File
config_file_path = Path(os.path.join(__location__, config_file_name))

# Read in configuration file.
if(config_file_path.is_file()):
    print("Configuration found in: {}".format(config_file_path))

    # Initiate the bot config object from file
    bot_config = BotConfig(config_file_path)
    print(str(bot_config))
else:
    print("The configuration file {} does not exist".format(config_file_path))

# Initialize the bot
bot = Bot(command_prefix=bot_config.command_prefix)

# Prep SQLAlchemy
engine = create_engine(bot_config.db_url, pool_recycle=3600)
session = Session(bind=engine)
Base.metadata.create_all(engine)

@bot.event
async def on_ready():
    '''Event for when the bot is ready to start working'''
    print("Ready when you are")
    print("I am running on " + bot.user.name)
    print("With the id " + bot.user.id)
    await bot.change_presence(game=discord.Game(name=bot_config.randompresence()))

@bot.command(pass_context=True)
async def ping(context, *args):
    '''Verifcation that the bot is running and working.'''
    await bot.say(":eight_spoked_asterisk: I'm here {}".format(
        context.message.author))
    # Remove the message that triggered this command
    await bot.delete_message(context.message)
    print("{} has pinged".format(context.message.author))

@bot.command(pass_context=True)
@commands.has_role('admin')
async def shutdown(context):
    '''Command to shut the bot down'''
    # Remove the message that triggered this command
    await bot.delete_message(context.message)
    await bot.logout()

@bot.command(pass_context=True)
async def status(context):
    ''' Reports pertinent bot statistics as an embed'''
    status_embed = discord.Embed(
        title="Kijiji Bot Status",
        description="Quick snapshot of what is going on with the bot",
        color=discord.Colour(randint(0, 16777215))
    ).add_field(
        name='Last DB Check',
        value=bot_config.last_searched
    )

    await bot.send_message(destination=context.message.channel, embed=status_embed)
    # Remove the message that triggered this command
    await bot.delete_message(context.message)

@bot.command(pass_context=True, aliases=['np'])
async def newpresence(context):
    '''Change the bot presence to another from from config'''

    # Get the current status of the bot so we can omit that from the choices.
    current_game = ([single_member.game.name for single_member in bot.get_all_members() if single_member.id == bot.user.id])[0]

    # Check to see if we have multiple options to choose from
    if len(bot_config.presence) > 1:
        # Same one could possibly show.
        await bot.change_presence(game=discord.Game(name=bot_config.randompresence(current_game)))
    else:
        await bot.say('I only have one presence.')

    # Remove the message that triggered this command
    await bot.delete_message(context.message)

@bot.command(pass_context=True, aliases=['gl'])
async def getlisting(context, id):
    '''Get a listing from the database matching the id passed'''
    try:
        single_listing = session.query(Listing).filter(Listing.id == id).first()
    except NoResultFound as e:
        print(e)
        await bot.say("No listings available")
        # Deal with that as wells

    if(single_listing):
        # print("channel:", bot_config.search[0].posting_channel.name, bot_config.search[0].posting_channel.id)
        await bot.send_message(destination=bot_config.search[0].posting_channel, embed=single_listing.to_embed())

    # Remove the message that triggered this command
    await bot.delete_message(context.message)


async def listing_watcher():
    ''' This is the looping task that will scan the database for new listings and post them to their appropriate channel'''
    await bot.wait_until_ready()

    while not bot.is_closed:
        # Process each search individually
        for single_search in bot_config.search:
            # Attempt to get new listings up to a certain number
            try:
                new_listings = session.query(Listing).filter(and_(Listing.new == 1, Listing.searchurlid.in_(single_search.search_indecies))).limit(bot_config.posting_limit)
            except NoResultFound as e:
                await bot.say("No listings available")

            if(new_listings):
                for new_listing in new_listings:
                    await bot.send_message(destination=single_search.posting_channel, embed=new_listing.to_embed())
                    # Flag the listing as old
                    new_listing.new = 0

                session.commit()

            # Update the last search value in config. Used in status command
            bot_config.last_searched = datetime.now()

            # Breather between search configs
            await asyncio.sleep(1)

        # task runs every 60 seconds
        await asyncio.sleep(60)

# Run the bot with the supplied token
print('Discord.py version:', discord.__version__)

# Start the database monitoring task
bot.loop.create_task(listing_watcher())
bot.run(bot_config.token)
bot.close()

botconfig.py

from json import load
from random import choice
from validators import url
from discord import Object

class SearchConfig(object):
    '''Coordinates the pulling of listings from the database and posting them'''
    __slots__ = 'id', 'search_indecies', 'posting_channel', 'thumbnail'

    def __init__(self, dictionary):

        if 'id' in dictionary.keys():
            self.id = dictionary['id']
        else:
            raise ValueError('Parameter "id" is required')

        if 'search_indecies' in dictionary.keys():
            self.search_indecies = [item for item in dictionary['search_indecies'] if isinstance(item, int)]

            # Verify that search contains at least one item
            if self.search_indecies.count == 0:
                raise ValueError('Parameter "search" contains no integers')
        else:
            raise ValueError('Parameter "search_indecies" is required')

        if 'posting_channel' in dictionary.keys():
            self.posting_channel = Object(id=dictionary['posting_channel'])
        else:
            raise ValueError('Parameter "posting_channel" is required')

        if 'thumbnail' in dictionary.keys():
            if url(dictionary['thumbnail']):
                self.thumbnail = dictionary['thumbnail']
            else:
                print(f"Thumbnail for {self.id} failed url validation")

    def __str__(self):
        return 'Id: {}nSearch Indecies: {}nPosting Channel: {}'.format(
            self.id,
            ", ".join([str(x) for x in self.search_indecies]),
            self.posting_channel
        )


class BotConfig(object):
    ''' Contains all import configuration used for the bot'''
    __slots__ = 'command_prefix', 'token', 'search', 'presence', 'db_url', 'posting_limit', 'last_searched'

    def __init__(self, path):
        '''Using the file path of the config file import and scrub settings '''

        # Set bot defaults where applicable
        defaults = dict(command_prefix='#', presence='hard to get', posting_limit=3)

        # Load from file
        with open(path) as json_file:
            config_options = load(json_file)

        # Check for the required token property
        if 'token' in config_options.keys():
            self.token = config_options['token']
        else:
            raise ValueError('Parameter "token" is required.')

        # Check for the optional posting_limit
        if 'posting_limit' in config_options.keys():
            self.posting_limit = config_options['posting_limit']
        else:
            self.posting_limit = defaults['posting_limit']

        # Get the required database url
        if 'db_url' in config_options.keys():
            self.db_url = config_options['db_url']
        else:
            raise ValueError('Parameter "db_url" is required.')

        # Check for the required search object property
        self.search = []
        if 'search' in config_options.keys():
            for search_config in config_options['search']:
                self.search.append(SearchConfig(dictionary=search_config))
        else:
            raise ValueError('At least one "search" is required.')

        # Set the command prefix from config if possible
        if "command_prefix" in config_options.keys():
            self.command_prefix = config_options["command_prefix"]
        else:
            self.command_prefix = defaults['command_prefix']

        # Load presences if any. Append default just in case
        self.presence = []
        if 'presence' in config_options.keys():
            self.presence = config_options['presence']

        if self.presence.count == 0:
            self.presence.append(defaults['presence'])

    def randompresence(self, *args):
        # Get a random presence from list
        return choice([option for option in self.presence if option not in args])

    def __str__(self):
        return 'Command Prefix: {} nSearch: {} nToken: {} nPresence: {}'.format(
            self.command_prefix,
            "n".join(str(x) for x in self.search),
            self.token,
            self.presence
        )

bot_cfg.json

{
    "token": "NDMwODA1O-FAKETOKEN-Mvsz-es",
    "command_prefix" : "?",
    "db_url": "mysql+pymysql://kijiji:zA8oCA1I88Wo@localhost/kijiji",
    "search": [
        {
            "id": "boardgames",
            "search_indecies" : [2],
            "posting_channel": "436277304260558848",
            "thumbnail" : "https://www.shareicon.net/data/128x128/2016/08/18/810389_strategy_512x512.png"
        }   
    ],
    "presence": [
        "with fire",
        "at full blast",
        "for keeps",
        "a trick on you",
        "Cupid",
        "Knify Spoony",
        "hooky",
        "it cool",
        "for all the marbles",
        "the market",
        "the trump card",
        "possum",
        "with loaded dice"
    ]
}

listing.py

from discord import Embed, Colour
from json import loads
from random import randint
from sqlalchemy import Column, String, Integer, Boolean, ForeignKey, DateTime
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()

# https://auth0.com/blog/sqlalchemy-orm-tutorial-for-python-developers/
class SearchURL(Base):
    __tablename__ = 'searchurls'
    urlid = Column(Integer, primary_key=True)
    url = Column(String)
    inserted = Column(DateTime)

    def __str__(self):
        return ('urlid:' + str(self.urlid) + 'n'
                'url:' + self.url + 'n'
                'inserted:' + str(self.inserted)
                )

class Listing(Base):
    __tablename__ = 'listings'

    id = Column(Integer, primary_key=True)
    url = Column(String)
    price = Column(String)
    title = Column(String)
    distance = Column(String)
    location = Column(String)
    posted = Column(DateTime)
    shortdescription = Column(String)
    lastsearched = Column(DateTime)
    searchurlid = Column(Integer, ForeignKey('searchurls.urlid'))
    imageurl = Column(String)
    discovered = Column(Integer)
    new = Column(Boolean)
    changes = Column(String)
    searchurl = relationship('SearchURL')

    def __str__(self):
        return ('id: ' + str(self.id) + "n" +
                'url: ' + self.url + "n" +
                'price: ' + self.price + "n" +
                'title: ' + self.title + "n" +
                'distance: ' + self.distance + "n" +
                'location: ' + self.location + "n" +
                'posted: ' + str(self.posted) + "n" +
                'shortdescription: ' + self.shortdescription + "n" +
                'lastsearched: ' + str(self.lastsearched) + "n" +
                'searchurlid: ' + str(self.searchurlid) + "n" +
                'imageurl: ' + self.imageurl + "n" +
                'discovered: ' + str(self.discovered) + "n" +
                'new: ' + str(self.new) + "n" +
                'changes: ' + self.changes + "n" +
                'searchurl: ' + str(self.searchurl)
                )

    def changes_to_string(self):
        '''Take the json string changes and convert it to formatted string for reading in embed'''
        changes_markdown = ''
        if(self.changes):
            for change in loads(self.changes):
                changes_markdown += f'_{str(change["Property"])}:_ {str(change["Findings"])}n'
        return changes_markdown

    def to_embed(self, **kwargs):
        '''Created a discord embed from this instances properties'''
        # If this listing has been discovered before then it is possible there are changes
        # that should be shown in the message as well.
        if(self.discovered > 0):
            listing_description = (
                f'{self.shortdescription}nn'
                f'This listing has been found {self.discovered} time(s) before.n'
            )
            if self.changes:
                listing_description += (
                    f'**The following differences from the previous listing were identified**n'
                    f'{self.changes_to_string()}'
                )
        else:
            listing_description = self.shortdescription

        listing_as_embed = Embed(
            title=self.title, description=listing_description, color=Colour(randint(0, 16777215)),
            url=f'https://www.kijiji.ca{self.url}')
        listing_as_embed.add_field(name='Location', value=self.location, inline=True)
        listing_as_embed.add_field(name='Price', value=self.price, inline=True)
        # This url might contain a tilde which Discord will have an issue with
        # Replace the tilde with a URL encoded version
        listing_as_embed.set_image(url=self.imageurl.replace('~', '%7E'))
        listing_as_embed.set_footer(text='Listed: {}'.format(self.posted))
        if 'thumbnail' in kwargs:
            listing_as_embed.set_thumbnail(url=kwargs.get('thumbnail'))

        return listing_as_embed

Since this is my first project I know there are areas of improvement. This was my first look at asynchronous programming, discord.py, SQLAlchemy, and most of the other concepts used here. Those and how I am dealing with JSON would be appreciated areas of focus.

I used Anaconda in Sublime Text so most of the PEP8 stuff should be good. I did disable E501 (Line length), E302(double blank after imports) checks as I found that mostly annoying.

Environment Details

  • Python 3.6.5
  • Discord.py 0.16.12
  • MariaDB 10.2.12-MariaDB


Get this bounty!!!

#StackBounty: #django #python-3.x Object in django admin for a foreignkey :

Bounty: 100

I have this object in my django model :

class Stock(models.Model):
    name =  models.CharField(null=True,blank=True,max_length=20)
    def ___str__(self):
        return self.name
    def __unicode__(self):
        return self.name

When I see it in the django admin it works well.

But in the admin when I want to see this object :

class StockData(models.Model):
    stock = models.ForeignKey(Stock,on_delete=models.PROTECT,null=True,blank=True)
    date = models.DateTimeField(null=True,blank=True)
    interval = models.CharField(null=True, blank=True,max_length=2)

    def ___str__(self):
        return self.stock+ ":" + str(self.date)
    def __unicode__(self):
        return self.stock + ":" + str(self.date)

with this admin :

from django.contrib import admin
from .models import StockData

# Register your models here.
class StockData_Admin(admin.ModelAdmin):
    list_display = (
        'date',
        'stock',
        'interval'
    )

admin.site.register(StockData,StockData_Admin)

but in the admin page the stock object is represented as :

Stock object (1)

edit :

enter image description here

So how to solve this ?

My django version :

>>> import django
>>> django.VERSION 
(2, 0, 0, 'final', 0)

Thanks and regards


Get this bounty!!!

#StackBounty: #python #python-3.x #oop #monkeypatching Is it possible to fully Monkey Patch builtin `str`

Bounty: 50

I Am trying to patch python’s built-in str in order to track the count of all strallocations. I am running into some issues and was wondering if anyone could see what I’m doing wrong, or if this is even possible natively through monkey patching in python3?

$ python
Python 3.5.2 (default, Nov 23 2017, 16:37:01)
[GCC 5.4.0 20160609] on linux

I first naively tried to patch str as if it were a function:

def patch_str_allocations():
  old_str = str
  def mystr(*args, **kwargs):
    return old_str(*args, **kwargs)

  builtins.str = mystr


def test():
  logger = logging.getLogger(__name__)
  patch_str_allocations()
  logger.debug(str('test'))

But of course this fails all sorts of operations that string is used for like isinstance

    logger.debug(route)
  File "/usr/lib/python3.5/logging/__init__.py", line 1267, in debug
    self._log(DEBUG, msg, args, **kwargs)
  File "/usr/lib/python3.5/logging/__init__.py", line 1403, in _log
    fn, lno, func, sinfo = self.findCaller(stack_info)
  File "/usr/lib/python3.5/logging/__init__.py", line 1360, in findCaller
    filename = os.path.normcase(co.co_filename)
  File "/home/ubuntu/.virtualenvs/papm/lib/python3.5/posixpath.py", line 52, in normcase
    if not isinstance(s, (bytes, str)):
TypeError: isinstance() arg 2 must be a type or tuple of types

I then tried a class based approach:

class StrAllocator(str):
    oldstr = None

    def __new__(cls, *args, **kwargs):
        return StrAllocator.oldstr.__new__(cls, *args, **kwargs)

    @property
    def __class__(self):
        return str

    def __repr__(self):
        return self.__class__.__name__ + str.__repr__(self)


def patch_str_allocations():
    StrAllocator.oldstr = str
    builtins.str = StrAllocator

In normal str construction this is working OK but am still running into some issues:

class StrAllocatorTestCase(unittest.TestCase):

    def test_log(self):
        t1 = str('t1')
        logger = logging.getLogger(__name__)
        patch_str_allocations()
        t2 = str('t2')
        print(type(t1))
        print(type(t2))
        print(isinstance(t1, str))
        print(isinstance(t2, StrAllocator))
        print(isinstance(t2, str))
        logger.debug(str('test'))


$ nosetests tests.test_str_allocator:StrAllocatorTestCase.test_log -s

<class 'str'>
<class 'pythonapm.instruments.allocations.StrAllocator'>
False
True
True
E
======================================================================
ERROR: test_log (tests.instruments.test_str_allocator.StrAllocatorTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/vagrant_data/github.com/dm03514/python-apm/tests/instruments/test_str_allocator.py", line 30, in test_log
    logger.debug(str('test'))
  File "/usr/lib/python3.5/logging/__init__.py", line 1267, in debug
    self._log(DEBUG, msg, args, **kwargs)
  File "/usr/lib/python3.5/logging/__init__.py", line 1403, in _log
    fn, lno, func, sinfo = self.findCaller(stack_info)
  File "/usr/lib/python3.5/logging/__init__.py", line 1360, in findCaller
    filename = os.path.normcase(co.co_filename)
  File "/home/ubuntu/.virtualenvs/papm/lib/python3.5/posixpath.py", line 54, in normcase
    "not '{}'".format(s.__class__.__name__))
TypeError: normcase() argument must be str or bytes, not 'str'

----------------------------------------------------------------------
Ran 1 test in 0.003s

Can anyone see what’s missing? (besides my understanding of descriptors and python classes :p )


From the REPL the example above Works, but it does not work within the context of nose and unittests…

⟫ ipython
Python 3.5.2 (default, Nov 23 2017, 16:37:01)
Type 'copyright', 'credits' or 'license' for more information
IPython 6.4.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: import logging

In [2]: import builtins

In [3]: class StrAllocator(str):
   ...:     oldstr = None
   ...:
   ...:     def __new__(cls, *args, **kwargs):
   ...:         return StrAllocator.oldstr.__new__(cls, *args, **kwargs)
   ...:
   ...:     @property
   ...:     def __class__(self):
   ...:         return str
   ...:
   ...:     def __repr__(self):
   ...:         return self.__class__.__name__ + str.__repr__(self)
   ...:
In [4]: def patch_str_allocations():                                                                                                                                    [6/9733]
   ...:     StrAllocator.oldstr = str
   ...:     builtins.str = StrAllocator
   ...:

In [5]:   def test_log():
   ...:         t1 = str('t1')
   ...:         logger = logging.getLogger(__name__)
   ...:         patch_str_allocations()
   ...:         t2 = str('t2')
   ...:         print(type(t1))
   ...:         print(type(t2))
   ...:         print(isinstance(t1, str))
   ...:         print(isinstance(t2, StrAllocator))
   ...:         print(isinstance(t2, str))
   ...:         logger.debug(str('test'))
   ...:
In [6]: test_log()
<class 'str'>
<class '__main__.StrAllocator'>
False
True
True


Get this bounty!!!

#StackBounty: #python #object-oriented #python-3.x #numpy #pandas Warhammer: How many of my attacks will succeed?

Bounty: 100

Me and a couple of mates sometimes play a game called Warhammer.
When playing the game you have options of what each model attacks.
This can lead to situations where you know if you shoot with 100% of your units into one enemy unit you know the unit will be killed, but you don’t know how many you should fire to kill the target unit.
And so I decided to write a little program that would help find out how many models I should shoot with into an enemy.

Combat in Warhammer is pretty basic, however some added complexity can come from additional rules on specific units or weapons.
The core rules when attacking another unit with a model is:

  1. Choose a Model to fight with
  2. Choose the Unit(s) to attack
  3. Choose the weapons you’ll attack with
  4. Resolve Attacks:
    1. Hit roll: for each attack roll a dice, if the roll is greater or equal to the attacking models Skill the attack hits.
    2. Wound roll: This is the same as hitting, however what you roll is based on the weapons Strength and the targets Toughness.
      • S >= 2T: 2+
      • S > T: 3+
      • S == T: 4+
      • S < T: 5+
      • S <= T: 6+
    3. Allocate wound: You select a model to try and resist the wound.

    4. Saving Throw: Roll a dice and add armor penetration to the roll, if it’s greater than the models save then no damage is inflicted.

      There are also ‘invulnerable saves’, which work the same way as normal saves, but aren’t affected by armor penetration.

    5. Inflict Damage: The model takes the weapons damage, if the unit is reduced to 0 wounds it dies.

An example of this is:

  1. We select a Khorne Berzerker

    $
    begin{array}{l|l|l|l|l}
    textrm{Skill} &
    textrm{S} &
    textrm{T} &
    textrm{W} &
    textrm{Sv} \
    hline
    text{3+} &
    text{5} &
    text{4} &
    text{1} &
    text{3+} \
    end{array}
    $

  2. We attack a squad of Khorne Berzerkers

  3. We will attack with it’s Chainaxe

    $
    begin{array}{l|l|l|l}
    textrm{Attacks} &
    textrm{S} &
    textrm{AP} &
    textrm{D} \
    hline
    text{1} &
    text{6} &
    text{-1} &
    text{1} \
    end{array}
    $

    1. I roll a 3. This is equal to the models Skill.
    2. I roll a 3. This is equal to the required roll. (6 > 4: 3+)
    3. A Khorne Berzerker is selected to take the wound.
    4. My opponent rolls a 3. And since $3 – 1 < 3$, the save is failed, and the wound goes through.
    5. One enemy model dies.

There are some additional common effects:

  • Some units allow others to re-roll failed hit rolls, hit rolls of one, failed wound rolls and wound rolls of one. However you can only re-roll a roll once, so you couldn’t re-roll a hit of 1 and then re-roll a hit of 2. But you can re-roll a failed hit and then re-roll a failed wound.
  • Some things allow you to add to your hit and wound rolls.
  • Some things allow you to skip your hit or wound phase. Flame throwers normally auto hit, and so skip their hit phase.

And so I wrote some code to show the percentage of attacks that will be lost, and at what stage.
And the average amount of attacks and damage each weapon will have.

from functools import wraps
import enum
from collections import Counter
from itertools import product

import pandas as pd
import matplotlib.pyplot as plt
import numpy as np


class TypedProperty:
    def __init__(self, name, *types):
        types = [type(None) if t is None else t for t in types]
        if not all(isinstance(t, type) for t in types):
            raise ValueError('All arguments to `types` must inherit from type.')
        self.types = tuple(types)
        self.name = name

    def __get__(self, obj, _):
        return self._get(obj, self.name)

    def __set__(self, obj, value):
        if not isinstance(value, self.types):
            raise TypeError('Value {value} must inherit one of {self.types}'.format(value=value, self=self))
        self._set(obj, self.name, value)

    def __delete__(self, obj):
        self._delete(obj, self.name)

    def get(self, fn):
        self._get = fn
        return self

    def set(self, fn):
        self._set = fn
        return self

    def delete(self, fn):
        self._delete = fn
        return self

    @staticmethod
    def _get(self, name):
        return getattr(self, name)

    @staticmethod
    def _set(self, name, value):
        setattr(self, name, value)

    @staticmethod
    def _delete(self, name):
        delattr(self, name)


class Damage(tuple):
    def __new__(self, value):
        if isinstance(value, tuple):
            pass
        elif isinstance(value, int):
            value = (value, None)
        elif not isinstance(value, str):
            raise TypeError('Value must be an int, tuple or str')
        else:
            value = tuple(value.split('d', 1) + [None])[:2]
            value = (i or None for i in value)

        value = tuple(int(i) if i is not None else 1 for i in value)
        return super().__new__(self, value)


class Effects(enum.Enum):
    SKIP_HIT = 0
    HIT_ONE = 1
    HIT_FAILED = 2
    WOUND_ONE = 3
    WOUND_FAILED = 4


class Base:
    _INIT = tuple()

    def __init__(self, *args, **kwargs):
        values = self._read_args(args, kwargs)
        for name in self._INIT:
            setattr(self, name, values.get(name, None))

    def _read_args(self, args, kwargs):
        values = dict(zip(self._INIT, args))
        values.update(kwargs)
        return values


class User(Base):
    _INIT=tuple('skill'.split())
    skill=TypedProperty('_skill', int)


class Weapon(Base):
    _INIT=tuple('attacks strength ap damage'.split())
    attacks=TypedProperty('_attacks', Damage)
    strength=TypedProperty('_strength', int)
    ap=TypedProperty('_ap', int)
    damage=TypedProperty('_damage', Damage)


class Target(Base):
    _INIT=tuple('toughness save invulnerable'.split())
    toughness=TypedProperty('_toughness', int)
    save=TypedProperty('_save', int)
    invulnerable=TypedProperty('_invulnerable', int, None)


class RoundEffects(Base):
    _INIT=tuple('skip one failed increase'.split())
    skip=TypedProperty('_skip', bool, None)
    one=TypedProperty('_one', bool, None)
    failed=TypedProperty('_failed', bool, None)
    increase=TypedProperty('_increase', int, None)

    def reroll(self, score):
        if self.failed:
            return score
        if self.one:
            return 1
        return 0

    def round(self, score):
        if self.skip:
            return None
        return (
            score + (self.increase or 0),
            self.reroll(score)
        )


class Effects(Base):
    _INIT=tuple('hit wound'.split())
    hit=TypedProperty('_hit', RoundEffects)
    wound=TypedProperty('_wound', RoundEffects)

    def __init__(self, *args, **kwargs):
        kwargs = self._read_args(args, kwargs)
        for key in 'hit wound'.split():
            if kwargs.get(key, None) is None:
                kwargs[key] = RoundEffects()
        super().__init__(**kwargs)


class Instance(Base):
    _INIT=tuple('user weapon target effects'.split())
    user=TypedProperty('_user', User)
    weapon=TypedProperty('_weapon', Weapon)
    target=TypedProperty('_target', Target)
    effects=TypedProperty('_effects', Effects)

    def __init__(self, *args, **kwargs):
        kwargs = self._read_args(args, kwargs)
        if kwargs.get('effects', None) is None:
            kwargs['effects'] = Effects()
        super().__init__(**kwargs)

    def _damage(self, damage):
        amount, variable = damage
        variable = tuple(range(1, variable+1))
        return [sum(ns) for ns in product(variable, repeat=amount)]

    def attacks(self):
        return self._damage(self.weapon.attacks)

    def shots(self):
        return self.weapon.attacks

    def hits(self):
        return self.effects.hit.round(self.user.skill)

    def _round(self, damage):
        if damage is None:
            return (0, 100)
        needed, reroll = damage
        values = tuple(range(6))
        rolls = np.array([
            v
            for n in values
            for v in (values if n < reroll else [n] * 6)
        ])
        ratio = np.bincount(rolls >= needed)
        return ratio * 100 / np.sum(ratio)

    def hits_wl(self):
        return self._round(self.hits())

    def damage_roll(self):
        s = self.weapon.strength
        t = self.target.toughness
        if s >= t * 2:
            return 2
        if s > t:
            return 3
        if s == t:
            return 4
        if s * 2 <= t:
            return 6
        if s < t:
            return 5

    def wounds(self):
        return self.effects.wound.round(self.damage_roll())

    def wounds_wl(self):
        return self._round(self.wounds())

    def save(self):
        return min(
            self.target.save - self.weapon.ap,
            self.target.invulnerable or 7
        )

    def save_wl(self):
        save = self.save()
        ratio = np.array((7 - save, save - 1))
        return ratio * 100 / np.sum(ratio)

    def win_loss(self):
        wls = [
            self.hits_wl(),
            self.wounds_wl(),
            self.save_wl()
        ]
        failed = 0
        for loss, _ in wls:
            win = 100 - failed
            loss = loss * win / 100
            yield loss
            failed += loss
        yield 100 - failed

    def damage(self):
        return self._damage(self.weapon.damage)


def plot(instance):
    fig, axes = plt.subplots(1, 3)

    win_loss = list(instance.win_loss())
    df = pd.DataFrame(
        [
            win_loss[:1] + [0, 0] + [sum(win_loss[1:])],
            win_loss[:2] + [0] + [sum(win_loss[2:])],
            win_loss
        ],
        columns=['Miss', 'Prevented', 'Saved', 'Passed'],
        index=['Hit', 'Wound', 'Save']
    )
    df.plot.bar(stacked=True, ax=axes[1]).set_ylim(0, 100)

    attacks = instance.attacks()
    damage = instance.damage()
    limit = max(max(attacks), max(damage))
    limit = int((limit + 1) * 1.1)

    pd.DataFrame(attacks).boxplot(return_type='axes', ax=axes[0]).set_ylim(0, limit)
    pd.DataFrame(damage).boxplot(return_type='axes', ax=axes[2]).set_ylim(0, limit)



if __name__ == '__main__':
    khorn = Instance(
        User(skill=3),
        Weapon(
            attacks=Damage(2),
            strength=6,
            ap=-1,
            damage=Damage(1)
        ),
        Target(
            toughness=4,
            save=3
        ),
        Effects(
            RoundEffects(
                failed=True
            ),
            RoundEffects(
                failed=True
            )
        )
    )
    plot(khorn)

    khorn2 = Instance(
        User(skill=3),
        Weapon(
            attacks=Damage(2),
            strength=6,
            ap=-1,
            damage=Damage(1)
        ),
        Target(
            toughness=4,
            save=3
        )
    )
    plot(khorn2)

    land = Instance(
        User(skill=3),
        Weapon(
            attacks=Damage(2),
            strength=9,
            ap=-3,
            damage=Damage('d6')
        ),
        Target(
            toughness=7,
            save=3
        )
    )
    plot(land)

    predator = Instance(
        User(skill=3),
        Weapon(
            attacks=Damage('2d3'),
            strength=7,
            ap=-1,
            damage=Damage('3')
        ),
        Target(
            toughness=7,
            save=3
        )
    )
    plot(predator)

    plt.show()


Get this bounty!!!

#StackBounty: #python #python-3.x #security #authentication #django Let's register that Django user

Bounty: 200

Short intro

So, I’ve been using Django for a while now and thought it would be nice to start a simple application. In an ideal World, each app must have a way of letting its users register and that’s what I’m gonna do below.


Description

I’ve used the latest Django version for this project -> because of this, I couldn’t use django-registration module as it’s incompatible with the above. Apart from this, I’ve used Python 3.6.5.

What I want to do it’s common along other web applications:

  1. The user goes to the registration page and fills in the following fields:
    • first_name
    • last_name
    • email
  2. The user submits the form and receives a confirmation email with an URL containing a unique token.

  3. When the user clicks on the received link, he’s redirected to a page where he’ll set his password.

  4. When done, he’s logged in to the dashboard page.


Code

models.py

from django.contrib.auth.models import AbstractBaseUser
from django.contrib.auth.models import BaseUserManager
from django.contrib.auth.models import PermissionsMixin
from django.db import models
from django.utils.translation import ugettext_lazy as _


class UserManager(BaseUserManager):
    """
    A custom user manager to deal with emails as unique identifiers for auth
    instead of usernames. The default that's used is "UserManager"
    """

    def _create_user(self, email, password, **extra_fields):
        """
        Creates and saves a User with the given email and password.
        """

        if not email:
            raise ValueError('The Email must be set')

        email = self.normalize_email(email)
        user = self.model(email=email, **extra_fields)
        user.set_password(password)
        user.save(using=self._db)

        return user

    def create_user(self, email, password=None, **extra_fields):
        """
        Create and save a regular User with the given email and password.
        """

        extra_fields.setdefault('is_staff', False)
        extra_fields.setdefault('is_superuser', False)

        return self._create_user(email, password, **extra_fields)

    def create_superuser(self, email, password, **extra_fields):
        """
        Create and save a SuperUser with the given email and password.
        """

        extra_fields.setdefault('is_staff', True)
        extra_fields.setdefault('is_superuser', True)
        extra_fields.setdefault('is_active', True)

        if extra_fields.get('is_staff') is not True:
            raise ValueError('Superuser must have is_staff=True.')

        if extra_fields.get('is_superuser') is not True:
            raise ValueError('Superuser must have is_superuser=True.')

        return self._create_user(email, password, **extra_fields)


class User(AbstractBaseUser, PermissionsMixin):
    email = models.EmailField(unique=True, null=True, max_length=64)
    first_name = models.CharField(max_length=64)
    last_name = models.CharField(max_length=64)

    is_staff = models.BooleanField(
        _('staff status'),
        default=False,
        help_text=_('Designates whether the user can log into this site.'),
    )
    is_active = models.BooleanField(
        _('active'),
        default=True,
        help_text=_(
            'Designates whether this user should be treated as active. '
            'Unselect this instead of deleting accounts.'
        ),
    )
    USERNAME_FIELD = 'email'
    objects = UserManager()

    def __str__(self):
        return self.email

    def get_full_name(self):
        return self.email

    def get_short_name(self):
        return self.email

forms.py

from django.contrib.auth import get_user_model
from django.forms import ModelForm

User = get_user_model()


class RegistrationForm(ModelForm):

    class Meta:
        model = User
        fields = ('email', 'first_name', 'last_name')

    def save(self, commit=True):
        user = super(RegistrationForm, self).save(commit=False)
        if commit:
            user.save()
        return user

views.py

from django.contrib import messages
from django.contrib.auth import update_session_auth_hash, get_user_model, authenticate, login
from django.contrib.auth.forms import AuthenticationForm, SetPasswordForm
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.views import LoginView, LogoutView
from django.contrib.sites.shortcuts import get_current_site
from django.core.mail import EmailMessage
from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.template.loader import render_to_string
from django.utils.encoding import force_bytes, force_text
from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode
from django.views.generic import TemplateView, View

from .forms import RegistrationForm
from .tokens import account_activation_token

User = get_user_model()


class HomeView(LoginRequiredMixin, TemplateView):
    template_name = 'base/home.html'


class CustomLoginView(LoginView):
    template_name = 'base/login.html'
    form_class = AuthenticationForm


class CustomLogoutView(LogoutView):
    template_name = 'base/login.html'


class RegistrationView(View):

    def get(self, request):
        registration_form = RegistrationForm()
        return render(request, 'base/register.html', {'form': registration_form})

    def post(self, request):
        registration_form = RegistrationForm(request.POST)

        if registration_form.is_valid():
            user = registration_form.save(commit=False)
            user.is_active = False
            user.set_unusable_password()
            user.save()

            current_site = get_current_site(request)
            mail_subject = 'Activate your account.'
            message = render_to_string('base/activation-email.html', {
                'user': user,
                'domain': current_site.domain,
                'uid': urlsafe_base64_encode(force_bytes(user.pk)).decode(),
                'token': account_activation_token.make_token(user),
            })
            to_email = registration_form.cleaned_data.get('email')
            email = EmailMessage(
                mail_subject, message, to=[to_email]
            )
            email.send()

            return render(request, 'base/confirm-email.html')


class ActivationView(View):

    def get(self, request, uidb64, token):
        try:
            uid = force_text(urlsafe_base64_decode(uidb64))
            user = User.objects.get(pk=uid)
        except(TypeError, ValueError, OverflowError, User.DoesNotExist):
            user = None

        if user is not None and account_activation_token.check_token(user, token):
            user.is_active = True
            user.save()
            login(request, user)

            form = SetPasswordForm(request.user)
            return render(request, 'base/password-set.html', {'form': form})
        else:
            return render(request, 'base/verification-failed.html')

    def post(self, request, uidb64, token):

        try:
            uid = force_text(urlsafe_base64_decode(uidb64))
            user = User.objects.get(pk=uid)
        except(TypeError, ValueError, OverflowError, User.DoesNotExist):
            user = None

        form = SetPasswordForm(user, request.POST)

        if form.is_valid():
            user = form.save()
            update_session_auth_hash(request, user)

            user = authenticate(username=user.email, password=form.cleaned_data['new_password1'])
            login(request, user)

            return HttpResponseRedirect('/')
        else:
            messages.error(request, form.errors)
            return render(request, 'base/password-set.html', {'form': form})

urls.py

from django.conf.urls import url
from django.views.generic import TemplateView

from .views import (
    CustomLoginView, CustomLogoutView, HomeView, 
    RegistrationView, ActivationView
)

app_name = 'apps.base'

urlpatterns = [
    url(r'^$', HomeView.as_view(), name='home'),

    url(r'^login/$', CustomLoginView.as_view(), name='login'),
    url(r'^logout/$', CustomLogoutView.as_view(), name='logout'),

    url(r'^register/$', RegistrationView.as_view(), name='register'),
    url(r'^confirm_email/$', TemplateView.as_view(template_name='base/confirm-email.html'), name='confirm_email'),
    url(r'^activate/(?P<uidb64>[0-9A-Za-z_-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
        ActivationView.as_view(), name='activate'),
]

tokens.py

from django.contrib.auth.tokens import PasswordResetTokenGenerator
from django.utils import six


class TokenGenerator(PasswordResetTokenGenerator):
    def _make_hash_value(self, user, timestamp):
        return (
            six.text_type(user.pk) + six.text_type(timestamp) +
            six.text_type(user.is_active)
        )


account_activation_token = TokenGenerator()

Things that I’d like to be reviewed:

  • Django Design Patterns: Did I use the correct views / forms / models in my app? Should some of the logic be written somewhere else etc…
  • Security: Is my register system secure? If not, what can I do to make it so?
  • General feedback: Anything that comes to your mind is welcome (including criticism, ideas, improvements, cats…aliens..)


Get this bounty!!!

#StackBounty: #python #python-3.x #security #authentication #django Let's register that Django user

Bounty: 200

Short intro

So, I’ve been using Django for a while now and thought it would be nice to start a simple application. In an ideal World, each app must have a way of letting its users register and that’s what I’m gonna do below.


Description

I’ve used the latest Django version for this project -> because of this, I couldn’t use django-registration module as it’s incompatible with the above. Apart from this, I’ve used Python 3.6.5.

What I want to do it’s common along other web applications:

  1. The user goes to the registration page and fills in the following fields:
    • first_name
    • last_name
    • email
  2. The user submits the form and receives a confirmation email with an URL containing a unique token.

  3. When the user clicks on the received link, he’s redirected to a page where he’ll set his password.

  4. When done, he’s logged in to the dashboard page.


Code

models.py

from django.contrib.auth.models import AbstractBaseUser
from django.contrib.auth.models import BaseUserManager
from django.contrib.auth.models import PermissionsMixin
from django.db import models
from django.utils.translation import ugettext_lazy as _


class UserManager(BaseUserManager):
    """
    A custom user manager to deal with emails as unique identifiers for auth
    instead of usernames. The default that's used is "UserManager"
    """

    def _create_user(self, email, password, **extra_fields):
        """
        Creates and saves a User with the given email and password.
        """

        if not email:
            raise ValueError('The Email must be set')

        email = self.normalize_email(email)
        user = self.model(email=email, **extra_fields)
        user.set_password(password)
        user.save(using=self._db)

        return user

    def create_user(self, email, password=None, **extra_fields):
        """
        Create and save a regular User with the given email and password.
        """

        extra_fields.setdefault('is_staff', False)
        extra_fields.setdefault('is_superuser', False)

        return self._create_user(email, password, **extra_fields)

    def create_superuser(self, email, password, **extra_fields):
        """
        Create and save a SuperUser with the given email and password.
        """

        extra_fields.setdefault('is_staff', True)
        extra_fields.setdefault('is_superuser', True)
        extra_fields.setdefault('is_active', True)

        if extra_fields.get('is_staff') is not True:
            raise ValueError('Superuser must have is_staff=True.')

        if extra_fields.get('is_superuser') is not True:
            raise ValueError('Superuser must have is_superuser=True.')

        return self._create_user(email, password, **extra_fields)


class User(AbstractBaseUser, PermissionsMixin):
    email = models.EmailField(unique=True, null=True, max_length=64)
    first_name = models.CharField(max_length=64)
    last_name = models.CharField(max_length=64)

    is_staff = models.BooleanField(
        _('staff status'),
        default=False,
        help_text=_('Designates whether the user can log into this site.'),
    )
    is_active = models.BooleanField(
        _('active'),
        default=True,
        help_text=_(
            'Designates whether this user should be treated as active. '
            'Unselect this instead of deleting accounts.'
        ),
    )
    USERNAME_FIELD = 'email'
    objects = UserManager()

    def __str__(self):
        return self.email

    def get_full_name(self):
        return self.email

    def get_short_name(self):
        return self.email

forms.py

from django.contrib.auth import get_user_model
from django.forms import ModelForm

User = get_user_model()


class RegistrationForm(ModelForm):

    class Meta:
        model = User
        fields = ('email', 'first_name', 'last_name')

    def save(self, commit=True):
        user = super(RegistrationForm, self).save(commit=False)
        if commit:
            user.save()
        return user

views.py

from django.contrib import messages
from django.contrib.auth import update_session_auth_hash, get_user_model, authenticate, login
from django.contrib.auth.forms import AuthenticationForm, SetPasswordForm
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.views import LoginView, LogoutView
from django.contrib.sites.shortcuts import get_current_site
from django.core.mail import EmailMessage
from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.template.loader import render_to_string
from django.utils.encoding import force_bytes, force_text
from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode
from django.views.generic import TemplateView, View

from .forms import RegistrationForm
from .tokens import account_activation_token

User = get_user_model()


class HomeView(LoginRequiredMixin, TemplateView):
    template_name = 'base/home.html'


class CustomLoginView(LoginView):
    template_name = 'base/login.html'
    form_class = AuthenticationForm


class CustomLogoutView(LogoutView):
    template_name = 'base/login.html'


class RegistrationView(View):

    def get(self, request):
        registration_form = RegistrationForm()
        return render(request, 'base/register.html', {'form': registration_form})

    def post(self, request):
        registration_form = RegistrationForm(request.POST)

        if registration_form.is_valid():
            user = registration_form.save(commit=False)
            user.is_active = False
            user.set_unusable_password()
            user.save()

            current_site = get_current_site(request)
            mail_subject = 'Activate your account.'
            message = render_to_string('base/activation-email.html', {
                'user': user,
                'domain': current_site.domain,
                'uid': urlsafe_base64_encode(force_bytes(user.pk)).decode(),
                'token': account_activation_token.make_token(user),
            })
            to_email = registration_form.cleaned_data.get('email')
            email = EmailMessage(
                mail_subject, message, to=[to_email]
            )
            email.send()

            return render(request, 'base/confirm-email.html')


class ActivationView(View):

    def get(self, request, uidb64, token):
        try:
            uid = force_text(urlsafe_base64_decode(uidb64))
            user = User.objects.get(pk=uid)
        except(TypeError, ValueError, OverflowError, User.DoesNotExist):
            user = None

        if user is not None and account_activation_token.check_token(user, token):
            user.is_active = True
            user.save()
            login(request, user)

            form = SetPasswordForm(request.user)
            return render(request, 'base/password-set.html', {'form': form})
        else:
            return render(request, 'base/verification-failed.html')

    def post(self, request, uidb64, token):

        try:
            uid = force_text(urlsafe_base64_decode(uidb64))
            user = User.objects.get(pk=uid)
        except(TypeError, ValueError, OverflowError, User.DoesNotExist):
            user = None

        form = SetPasswordForm(user, request.POST)

        if form.is_valid():
            user = form.save()
            update_session_auth_hash(request, user)

            user = authenticate(username=user.email, password=form.cleaned_data['new_password1'])
            login(request, user)

            return HttpResponseRedirect('/')
        else:
            messages.error(request, form.errors)
            return render(request, 'base/password-set.html', {'form': form})

urls.py

from django.conf.urls import url
from django.views.generic import TemplateView

from .views import (
    CustomLoginView, CustomLogoutView, HomeView, 
    RegistrationView, ActivationView
)

app_name = 'apps.base'

urlpatterns = [
    url(r'^$', HomeView.as_view(), name='home'),

    url(r'^login/$', CustomLoginView.as_view(), name='login'),
    url(r'^logout/$', CustomLogoutView.as_view(), name='logout'),

    url(r'^register/$', RegistrationView.as_view(), name='register'),
    url(r'^confirm_email/$', TemplateView.as_view(template_name='base/confirm-email.html'), name='confirm_email'),
    url(r'^activate/(?P<uidb64>[0-9A-Za-z_-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
        ActivationView.as_view(), name='activate'),
]

tokens.py

from django.contrib.auth.tokens import PasswordResetTokenGenerator
from django.utils import six


class TokenGenerator(PasswordResetTokenGenerator):
    def _make_hash_value(self, user, timestamp):
        return (
            six.text_type(user.pk) + six.text_type(timestamp) +
            six.text_type(user.is_active)
        )


account_activation_token = TokenGenerator()

Things that I’d like to be reviewed:

  • Django Design Patterns: Did I use the correct views / forms / models in my app? Should some of the logic be written somewhere else etc…
  • Security: Is my register system secure? If not, what can I do to make it so?
  • General feedback: Anything that comes to your mind is welcome (including criticism, ideas, improvements, cats…aliens..)


Get this bounty!!!