<commit_before>from django.utils.translation import ugettext_lazy as _
from django.core.urlresolvers import reverse
from django.conf import settings
from django.db import models
from django.utils.encoding import python_2_unicode_compatible

from markitup.fields import MarkupField

# constants to make things clearer elsewhere
ACCEPTED = 'A'
PENDING = 'P'
REJECTED = 'R'

@python_2_unicode_compatible
class TalkType(models.Model):
    """A type of talk."""
    name = models.CharField(max_length=255)
    description = models.TextField(max_length=1024)

    def __str__(self):
        return u'%s' % (self.name,)

@python_2_unicode_compatible
class Talk(models.Model):

    class Meta:
        permissions = (
            ("view_all_talks", "Can see all talks"),
        )

    TALK_STATUS = (
        (ACCEPTED, 'Accepted'),
        (REJECTED, 'Not Accepted'),
        (PENDING, 'Under Consideration'),
    )

    talk_id = models.AutoField(primary_key=True)
    talk_type = models.ForeignKey(TalkType, null=True)

    title = models.CharField(max_length=1024)

    abstract = MarkupField(
        help_text=_("Write two or three paragraphs describing your talk. "
                    "Who is your audience? What will they get out of it? "
                    "What will you cover?
"
                    "You can use Markdown syntax."))
    notes = models.TextField(
        null=True, blank=True,
        help_text=_("Any notes for the conference organisers?"))

    status = models.CharField(max_length=1, choices=TALK_STATUS,
                              default=PENDING)

    corresponding_author = models.ForeignKey(
        settings.AUTH_USER_MODEL, related_name='contact_talks')
    authors = models.ManyToManyField(settings.AUTH_USER_MODEL,
                                     related_name='talks')

    def __str__(self):
        return u'%s: %s' % (self.corresponding_author, self.title)

    def get_absolute_url(self):
        return reverse('wafer_talk', args=(self.talk_id,))

    def get_author_contact(self):
        email = self.corresponding_author.email
        profile = self.corresponding_author.get_profile()
        if profile.contact_number:
            contact = profile.contact_number
        else:
            # Should we wrap this in a span for styling?
            contact = 'NO CONTACT INFO'
        return '%s - %s' % (email, contact)
    get_author_contact.short_description = 'Contact Details'

    def get_author_name(self):
        return '%s (%s)' % (self.corresponding_author,
                            self.corresponding_author.get_full_name())

    get_author_name.admin_order_field = 'corresponding_author'
    get_author_name.short_description = 'Corresponding Author'

    def get_author_display_name(self):
        full_name = self.corresponding_author.get_full_name()
        if full_name:
            return full_name
        return self.corresponding_author.username

    def get_in_schedule(self):
        if self.scheduleitem_set.all():
            return True
        return False

    get_in_schedule.short_description = 'Added to schedule'
    get_in_schedule.boolean = True

    def has_url(self):
        """Test if the talk has urls associated with it"""
        if self.talkurl_set.all():
            return True
        return False

    has_url.boolean = True

    # Helpful properties for the templates
    accepted = property(fget=lambda x: x.status == ACCEPTED)
    pending = property(fget=lambda x: x.status == PENDING)
    reject = property(fget=lambda x: x.status == REJECTED)

    def can_view(self, user):
        if user.has_perm('talks.view_all_talks'):
            return True
        if self.authors.filter(username=user.username).exists():
            return True
        if self.accepted:
            return True
        return False

    @classmethod
    def can_view_all(cls, user):
        return user.has_perm('talks.view_all_talks')

    def can_edit(self, user):
        if user.has_perm('talks.change_talk'):
            return True
        if self.pending:
            if self.authors.filter(username=user.username).exists():
                return True
        return False

class TalkUrl(models.Model):
    """An url to stuff relevant to the talk - videos, slides, etc.

       Note that these are explicitly not intended to be exposed to the
       user, but exist for use by the conference organisers."""

    description = models.CharField(max_length=256)
    url = models.URLField()
    talk = models.ForeignKey(Talk)

if settings.WAFER_NEEDS_SOUTH:
    # Django 1.7 updates permissions automatically when migrate is run,
    # but South 1.0 still needs this to be explicitly hooked up like we
    # do here.
    from south.signals import post_migrate

    def update_permissions_after_migration(app, **kwargs):
        from django.db.models import get_app, get_models
        from django.contrib.auth.management import create_permissions

        create_permissions(get_app(app), get_models(),
                           verbosity=2 if settings.DEBUG else 0)

    post_migrate.connect(update_permissions_after_migration)
<commit_msg>fix use of get_profile in talks<commit_after>from django.utils.translation import ugettext_lazy as _
from django.core.urlresolvers import reverse
from django.conf import settings
from django.db import models
from django.utils.encoding import python_2_unicode_compatible

from markitup.fields import MarkupField

# constants to make things clearer elsewhere
ACCEPTED = 'A'
PENDING = 'P'
REJECTED = 'R'

@python_2_unicode_compatible
class TalkType(models.Model):
    """A type of talk."""
    name = models.CharField(max_length=255)
    description = models.TextField(max_length=1024)

    def __str__(self):
        return u'%s' % (self.name,)

@python_2_unicode_compatible
class Talk(models.Model):

    class Meta:
        permissions = (
            ("view_all_talks", "Can see all talks"),
        )

    TALK_STATUS = (
        (ACCEPTED, 'Accepted'),
        (REJECTED, 'Not Accepted'),
        (PENDING, 'Under Consideration'),
    )

    talk_id = models.AutoField(primary_key=True)
    talk_type = models.ForeignKey(TalkType, null=True)

    title = models.CharField(max_length=1024)

    abstract = MarkupField(
        help_text=_("Write two or three paragraphs describing your talk. "
                    "Who is your audience? What will they get out of it? "
                    "What will you cover?
"
                    "You can use Markdown syntax."))
    notes = models.TextField(
        null=True, blank=True,
        help_text=_("Any notes for the conference organisers?"))

    status = models.CharField(max_length=1, choices=TALK_STATUS,
                              default=PENDING)

    corresponding_author = models.ForeignKey(
        settings.AUTH_USER_MODEL, related_name='contact_talks')
    authors = models.ManyToManyField(settings.AUTH_USER_MODEL,
                                     related_name='talks')

    def __str__(self):
        return u'%s: %s' % (self.corresponding_author, self.title)

    def get_absolute_url(self):
        return reverse('wafer_talk', args=(self.talk_id,))

    def get_author_contact(self):
        email = self.corresponding_author.email
        profile = self.corresponding_author.userprofile
        if profile.contact_number:
            contact = profile.contact_number
        else:
            # Should we wrap this in a span for styling?
            contact = 'NO CONTACT INFO'
        return '%s - %s' % (email, contact)
    get_author_contact.short_description = 'Contact Details'

    def get_author_name(self):
        return '%s (%s)' % (self.corresponding_author,
                            self.corresponding_author.get_full_name())

    get_author_name.admin_order_field = 'corresponding_author'
    get_author_name.short_description = 'Corresponding Author'

    def get_author_display_name(self):
        full_name = self.corresponding_author.get_full_name()
        if full_name:
            return full_name
        return self.corresponding_author.username

    def get_in_schedule(self):
        if self.scheduleitem_set.all():
            return True
        return False

    get_in_schedule.short_description = 'Added to schedule'
    get_in_schedule.boolean = True

    def has_url(self):
        """Test if the talk has urls associated with it"""
        if self.talkurl_set.all():
            return True
        return False

    has_url.boolean = True

    # Helpful properties for the templates
    accepted = property(fget=lambda x: x.status == ACCEPTED)
    pending = property(fget=lambda x: x.status == PENDING)
    reject = property(fget=lambda x: x.status == REJECTED)

    def can_view(self, user):
        if user.has_perm('talks.view_all_talks'):
            return True
        if self.authors.filter(username=user.username).exists():
            return True
        if self.accepted:
            return True
        return False

    @classmethod
    def can_view_all(cls, user):
        return user.has_perm('talks.view_all_talks')

    def can_edit(self, user):
        if user.has_perm('talks.change_talk'):
            return True
        if self.pending:
            if self.authors.filter(username=user.username).exists():
                return True
        return False

class TalkUrl(models.Model):
    """An url to stuff relevant to the talk - videos, slides, etc.

       Note that these are explicitly not intended to be exposed to the
       user, but exist for use by the conference organisers."""

    description = models.CharField(max_length=256)
    url = models.URLField()
    talk = models.ForeignKey(Talk)

if settings.WAFER_NEEDS_SOUTH:
    # Django 1.7 updates permissions automatically when migrate is run,
    # but South 1.0 still needs this to be explicitly hooked up like we
    # do here.
    from south.signals import post_migrate

    def update_permissions_after_migration(app, **kwargs):
        from django.db.models import get_app, get_models
        from django.contrib.auth.management import create_permissions

        create_permissions(get_app(app), get_models(),
                           verbosity=2 if settings.DEBUG else 0)

    post_migrate.connect(update_permissions_after_migration)
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-

"""Formula controller"""

from datetime import datetime

import numpy as np
from openfisca_core import periods, simulations

from .. import contexts, conv, model, wsgihelpers

@wsgihelpers.wsgify
def api1_formula(req):
    API_VERSION = 1
    params = dict(req.GET)
    data = dict()

    try:
        period = parse_period(params.pop('period', None))
        params = normalize(params)
        column = get_column_from_formula_name(req.urlvars.get('name'))
        data['value'] = compute(column.name, params, period)
    except Exception as error:
        data['error'] = error.args[0]
    finally:
        return respond(req, API_VERSION, data, params)

@wsgihelpers.wsgify
def api2_formula(req):
    """
A simple `GET`-, URL-based API to OpenFisca, making the assumption of computing formulas for a single person.

Combination
-----------

You can compute several formulas at once by combining the paths and joining them with `+`.

Example:
```
/salsuperbrut+salaire_net_a_payer?salaire_de_base=1440
```

This will compute both `salsuperbrut` and `salaire_net_a_payer` in a single request.

URL size limit
--------------

Using combination with a lot of parameters may lead to long URLs.
If used within the browser, make sure the resulting URL is kept
[under 2047 characters](http://stackoverflow.com/questions/417142)
for cross-browser compatibility, by splitting combined requests.
On a server, just test what your library handles.
"""
    API_VERSION = '2.1.0'
    params = dict(req.GET)
    data = dict()

    try:
        params = normalize(params)
        formula_names = req.urlvars.get('names').split('+')

        data['values'] = dict()
        data['period'] = parse_period(req.urlvars.get('period'))

        for formula_name in formula_names:
            column = get_column_from_formula_name(formula_name)
            data['values'][formula_name] = compute(column.name, params, data['period'])

    except Exception as error:
        if isinstance(error.args[0], dict):  # we raised it ourselves, in this controller
            error = error.args[0]
        else:
            error = dict(
                message = unicode(error),
                code = 500
                )

        data['error'] = error
    finally:
        return respond(req, API_VERSION, data, params)

def get_column_from_formula_name(formula_name):
    result = model.tax_benefit_system.column_by_name.get(formula_name)
    if result is None:
        raise(Exception(dict(
            code = 404,
            message = u"You requested to compute variable '{}', but it does not exist"
                      .format(formula_name)
            )))

    if result.is_input_variable():
        raise(Exception(dict(
            code = 422,
            message = u"You requested to compute variable '{}', but it is an input variable, it cannot be computed"
                      .format(formula_name)
            )))

    return result

def normalize(params):
    result = dict()

    try:
        for param_name, value in params.items():
            result[param_name] = normalize_param(param_name, value)
    except KeyError:
        raise Exception(dict(
            code = 400,
            message = u"You passed parameter '{}', but it does not exist".format(param_name)
            ))

    return result

def normalize_param(name, value):
    column = model.tax_benefit_system.column_by_name[name]

    result, error = conv.pipe(
        column.input_to_dated_python  # if column is not a date, this will be None and conv.pipe will be pass-through
        )(value)

    if error is not None:
        raise Exception(dict(
            code = 400,
            message = u"Parameter '{}' could not be normalized: {}".format(name, error)
            ))

    return result

def parse_period(period_descriptor):
    period_descriptor = period_descriptor or default_period()

    try:
        result = periods.period(period_descriptor)
    except ValueError:
        raise(Exception(dict(
            code = 400,
            message = u"You requested computation for period '{}', but it could not be parsed as a period"
                      .format(period_descriptor)
            )))

    if result.unit not in ['year', 'month']:
        raise Exception(dict(
            code = 400,
            message = u"You passed period '{}', but it is not a month nor a year".format(period_descriptor)
            ))

    return result

def default_period():
    now = datetime.now()
    return '{}-{:02d}'.format(now.year, now.month)

# req: the original request we're responding to.
# version: a [semver](http://semver.org) identifier characterizing the version of the API.
# data: dict. Will be transformed to JSON and added to the response root.
#       `data` will be mutated. Currently considered acceptable because responding marks process end.
# params: dict. Parsed parameters. Will be echoed in the "params" key.
def respond(req, version, data, params):
    data.update(dict(
        apiVersion = version,
        params = params
        ))

    ctx = contexts.Ctx(req)

    return wsgihelpers.respond_json(
        ctx,
        data,
        headers = wsgihelpers.handle_cross_origin_resource_sharing(ctx),
        json_dumps_default = wsgihelpers.convert_date_to_json,
        )

def compute(formula_name, params, period):
    simulation = create_simulation(params, period)
    resulting_dated_holder = simulation.compute(formula_name)
    return resulting_dated_holder.to_value_json()[0]  # only one person => unwrap the array

def create_simulation(data, period):
    result = simulations.Simulation(
        debug = False,
        period = period,
        tax_benefit_system = model.tax_benefit_system,
        )
    # Initialize entities, assuming there is only one person and one of each other entities ("familles",
    # "foyers fiscaux", etc).
    for entity in result.entity_by_key_plural.itervalues():
        entity.count = 1
        entity.roles_count = 1
        entity.step_size = 1
    # Link person to its entities using ID & role.
    for entity in result.entity_by_key_plural.itervalues():
        if not entity.is_persons_entity:
            holder = result.get_or_new_holder(entity.index_for_person_variable_name)
            holder.put_in_cache(np.array([0]), period)
            holder = result.get_or_new_holder(entity.role_for_person_variable_name)
            holder.put_in_cache(np.array([0]), period)

    # Inject all variables from query string into arrays.
    for column_name, value in data.iteritems():
        column = model.tax_benefit_system.column_by_name[column_name]
        holder = result.get_or_new_holder(column_name)

        if period.unit == 'year':
            holder.put_in_cache(np.array([value], dtype = column.dtype), period)
        elif period.unit == 'month':
            # Inject inputs for all months of year
            year = period.start.year
            month_index = 1
            while month_index <= 12:
                month = periods.period('{}-{:02d}'.format(year, month_index))
                holder.put_in_cache(np.array([value], dtype = column.dtype), month)
                month_index += 1

    return result
<commit_msg>Update doc variable examples<commit_after># -*- coding: utf-8 -*-

"""Formula controller"""

from datetime import datetime

import numpy as np
from openfisca_core import periods, simulations

from .. import contexts, conv, model, wsgihelpers

@wsgihelpers.wsgify
def api1_formula(req):
    API_VERSION = 1
    params = dict(req.GET)
    data = dict()

    try:
        period = parse_period(params.pop('period', None))
        params = normalize(params)
        column = get_column_from_formula_name(req.urlvars.get('name'))
        data['value'] = compute(column.name, params, period)
    except Exception as error:
        data['error'] = error.args[0]
    finally:
        return respond(req, API_VERSION, data, params)

@wsgihelpers.wsgify
def api2_formula(req):
    """
A simple `GET`-, URL-based API to OpenFisca, making the assumption of computing formulas for a single person.

Combination
-----------

You can compute several formulas at once by combining the paths and joining them with `+`.

Example:
```
/salaire_super_brut+salaire_net_a_payer?salaire_de_base=1440
```

This will compute both `salaire_super_brut` and `salaire_net_a_payer` in a single request.

URL size limit
--------------

Using combination with a lot of parameters may lead to long URLs.
If used within the browser, make sure the resulting URL is kept
[under 2047 characters](http://stackoverflow.com/questions/417142)
for cross-browser compatibility, by splitting combined requests.
On a server, just test what your library handles.
"""
    API_VERSION = '2.1.0'
    params = dict(req.GET)
    data = dict()

    try:
        params = normalize(params)
        formula_names = req.urlvars.get('names').split('+')

        data['values'] = dict()
        data['period'] = parse_period(req.urlvars.get('period'))

        for formula_name in formula_names:
            column = get_column_from_formula_name(formula_name)
            data['values'][formula_name] = compute(column.name, params, data['period'])

    except Exception as error:
        if isinstance(error.args[0], dict):  # we raised it ourselves, in this controller
            error = error.args[0]
        else:
            error = dict(
                message = unicode(error),
                code = 500
                )

        data['error'] = error
    finally:
        return respond(req, API_VERSION, data, params)

def get_column_from_formula_name(formula_name):
    result = model.tax_benefit_system.column_by_name.get(formula_name)
    if result is None:
        raise(Exception(dict(
            code = 404,
            message = u"You requested to compute variable '{}', but it does not exist"
                      .format(formula_name)
            )))

    if result.is_input_variable():
        raise(Exception(dict(
            code = 422,
            message = u"You requested to compute variable '{}', but it is an input variable, it cannot be computed"
                      .format(formula_name)
            )))

    return result

def normalize(params):
    result = dict()

    try:
        for param_name, value in params.items():
            result[param_name] = normalize_param(param_name, value)
    except KeyError:
        raise Exception(dict(
            code = 400,
            message = u"You passed parameter '{}', but it does not exist".format(param_name)
            ))

    return result

def normalize_param(name, value):
    column = model.tax_benefit_system.column_by_name[name]

    result, error = conv.pipe(
        column.input_to_dated_python  # if column is not a date, this will be None and conv.pipe will be pass-through
        )(value)

    if error is not None:
        raise Exception(dict(
            code = 400,
            message = u"Parameter '{}' could not be normalized: {}".format(name, error)
            ))

    return result

def parse_period(period_descriptor):
    period_descriptor = period_descriptor or default_period()

    try:
        result = periods.period(period_descriptor)
    except ValueError:
        raise(Exception(dict(
            code = 400,
            message = u"You requested computation for period '{}', but it could not be parsed as a period"
                      .format(period_descriptor)
            )))

    if result.unit not in ['year', 'month']:
        raise Exception(dict(
            code = 400,
            message = u"You passed period '{}', but it is not a month nor a year".format(period_descriptor)
            ))

    return result

def default_period():
    now = datetime.now()
    return '{}-{:02d}'.format(now.year, now.month)

# req: the original request we're responding to.
# version: a [semver](http://semver.org) identifier characterizing the version of the API.
# data: dict. Will be transformed to JSON and added to the response root.
#       `data` will be mutated. Currently considered acceptable because responding marks process end.
# params: dict. Parsed parameters. Will be echoed in the "params" key.
def respond(req, version, data, params):
    data.update(dict(
        apiVersion = version,
        params = params
        ))

    ctx = contexts.Ctx(req)

    return wsgihelpers.respond_json(
        ctx,
        data,
        headers = wsgihelpers.handle_cross_origin_resource_sharing(ctx),
        json_dumps_default = wsgihelpers.convert_date_to_json,
        )

def compute(formula_name, params, period):
    simulation = create_simulation(params, period)
    resulting_dated_holder = simulation.compute(formula_name)
    return resulting_dated_holder.to_value_json()[0]  # only one person => unwrap the array

def create_simulation(data, period):
    result = simulations.Simulation(
        debug = False,
        period = period,
        tax_benefit_system = model.tax_benefit_system,
        )
    # Initialize entities, assuming there is only one person and one of each other entities ("familles",
    # "foyers fiscaux", etc).
    for entity in result.entity_by_key_plural.itervalues():
        entity.count = 1
        entity.roles_count = 1
        entity.step_size = 1
    # Link person to its entities using ID & role.
    for entity in result.entity_by_key_plural.itervalues():
        if not entity.is_persons_entity:
            holder = result.get_or_new_holder(entity.index_for_person_variable_name)
            holder.put_in_cache(np.array([0]), period)
            holder = result.get_or_new_holder(entity.role_for_person_variable_name)
            holder.put_in_cache(np.array([0]), period)

    # Inject all variables from query string into arrays.
    for column_name, value in data.iteritems():
        column = model.tax_benefit_system.column_by_name[column_name]
        holder = result.get_or_new_holder(column_name)

        if period.unit == 'year':
            holder.put_in_cache(np.array([value], dtype = column.dtype), period)
        elif period.unit == 'month':
            # Inject inputs for all months of year
            year = period.start.year
            month_index = 1
            while month_index <= 12:
                month = periods.period('{}-{:02d}'.format(year, month_index))
                holder.put_in_cache(np.array([value], dtype = column.dtype), month)
                month_index += 1

    return result
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
# Copyright (c) 2021, Frappe Technologies and contributors
# For license information, please see license.txt

import frappe
from frappe import _
from frappe.utils import get_link_to_form
from frappe.config import get_modules_from_app
from frappe.permissions import add_permission, add_user_permission
from frappe.model.document import Document

class UserType(Document):
	def validate(self):
		self.set_modules()
		self.add_select_perm_doctypes()

	def on_update(self):
		if self.is_standard:
			return

		self.validate_document_type_limit()
		self.validate_role()
		self.add_role_permissions_for_user_doctypes()
		self.add_role_permissions_for_select_doctypes()
		self.add_role_permissions_for_file()
		self.update_users()
		get_non_standard_user_type_details()
		self.remove_permission_for_deleted_doctypes()

	def on_trash(self):
		if self.is_standard:
			frappe.throw(_('Standard user type {0} can not be deleted.')
				.format(frappe.bold(self.name)))

	def set_modules(self):
		if not self.user_doctypes:
			return

		modules = frappe.get_all('DocType', fields=['distinct module as module'],
			filters={'name': ('in', [d.document_type for d in self.user_doctypes])})

		self.set('user_type_modules', [])
		for row in modules:
			self.append('user_type_modules', {
				'module': row.module
			})

	def validate_document_type_limit(self):
		limit = frappe.conf.get('user_type_doctype_limit', {}).get(frappe.scrub(self.name))

		if not limit and frappe.session.user != 'Administrator':
			frappe.throw(_('User does not have permission to create the new {0}')
				.format(frappe.bold(_('User Type'))), title=_('Permission Error'))

		if not limit:
			frappe.throw(_('The limit has not set for the user type {0} in the site config file.')
				.format(frappe.bold(self.name)), title=_('Set Limit'))

		if self.user_doctypes and len(self.user_doctypes) > limit:
			frappe.throw(_('The total number of user document types limit has been crossed.'),
				title=_('User Document Types Limit Exceeded'))

		custom_doctypes = [row.document_type for row in self.user_doctypes if row.is_custom]
		if custom_doctypes and len(custom_doctypes) > 3:
			frappe.throw(_('You can only set the 3 custom doctypes in the Document Types table.'),
				title=_('Custom Document Types Limit Exceeded'))

	def validate_role(self):
		if not self.role:
			frappe.throw(_("The field {0} is mandatory")
				.format(frappe.bold(_('Role'))))

		if not frappe.db.get_value('Role', self.role, 'is_custom'):
			frappe.throw(_("The role {0} should be a custom role.")
				.format(frappe.bold(get_link_to_form('Role', self.role))))

	def update_users(self):
		for row in frappe.get_all('User', filters = {'user_type': self.name}):
			user = frappe.get_cached_doc('User', row.name)
			self.update_roles_in_user(user)
			self.update_modules_in_user(user)
			user.update_children()

	def update_roles_in_user(self, user):
		user.set('roles', [])
		user.append('roles', {
			'role': self.role
		})

	def update_modules_in_user(self, user):
		block_modules = frappe.get_all('Module Def', fields = ['name as module'],
			filters={'name': ['not in', [d.module for d in self.user_type_modules]]})

		if block_modules:
			user.set('block_modules', block_modules)

	def add_role_permissions_for_user_doctypes(self):
		perms = ['read', 'write', 'create', 'submit', 'cancel', 'amend', 'delete']
		for row in self.user_doctypes:
			docperm = add_role_permissions(row.document_type, self.role)

			values = {perm:row.get(perm) or 0 for perm in perms}
			for perm in ['print', 'email', 'share']:
				values[perm] = 1

			frappe.db.set_value('Custom DocPerm', docperm, values)

	def add_select_perm_doctypes(self):
		if frappe.flags.ignore_select_perm:
			return

		self.select_doctypes = []

		select_doctypes = []
		user_doctypes = [row.document_type for row in self.user_doctypes]

		for doctype in user_doctypes:
			doc = frappe.get_meta(doctype)
			self.prepare_select_perm_doctypes(doc, user_doctypes, select_doctypes)

			for child_table in doc.get_table_fields():
				child_doc = frappe.get_meta(child_table.options)
				if not child_doc.istable:
					self.prepare_select_perm_doctypes(child_doc, user_doctypes, select_doctypes)

		if select_doctypes:
			select_doctypes = set(select_doctypes)
			for select_doctype in select_doctypes:
				self.append('select_doctypes', {
					'document_type': select_doctype
				})

	def prepare_select_perm_doctypes(self, doc, user_doctypes, select_doctypes):
		for field in doc.get_link_fields():
			if field.options not in user_doctypes:
				select_doctypes.append(field.options)

	def add_role_permissions_for_select_doctypes(self):
		for doctype in ['select_doctypes', 'custom_select_doctypes']:
			for row in self.get(doctype):
				docperm = add_role_permissions(row.document_type, self.role)
				frappe.db.set_value('Custom DocPerm', docperm,
					{'select': 1, 'read': 0, 'create': 0, 'write': 0})

	def add_role_permissions_for_file(self):
		docperm = add_role_permissions('File', self.role)
		frappe.db.set_value('Custom DocPerm', docperm,
			{'read': 1, 'create': 1, 'write': 1})

	def remove_permission_for_deleted_doctypes(self):
		doctypes = [d.document_type for d in self.user_doctypes]

		# Do not remove the doc permission for the file doctype
		doctypes.append('File')

		for doctype in ['select_doctypes', 'custom_select_doctypes']:
			for dt in self.get(doctype):
				doctypes.append(dt.document_type)

		for perm in frappe.get_all('Custom DocPerm',
			filters = {'role': self.role, 'parent': ['not in', doctypes]}):
			frappe.delete_doc('Custom DocPerm', perm.name)

def add_role_permissions(doctype, role):
	name = frappe.get_value('Custom DocPerm', dict(parent=doctype,
		role=role, permlevel=0))

	if not name:
		name = add_permission(doctype, role, 0)

	return name

def get_non_standard_user_type_details():
	user_types = frappe.get_all('User Type',
		fields=['apply_user_permission_on', 'name', 'user_id_field'],
		filters={'is_standard': 0})

	if user_types:
		user_type_details = {d.name: [d.apply_user_permission_on, d.user_id_field] for d in user_types}

		frappe.cache().set_value('non_standard_user_types', user_type_details)

		return user_type_details

@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_user_linked_doctypes(doctype, txt, searchfield, start, page_len, filters):
	modules = [d.get('module_name') for d in get_modules_from_app('frappe')]

	filters = [['DocField', 'options', '=', 'User'], ['DocType', 'is_submittable', '=', 0],
		['DocType', 'issingle', '=', 0], ['DocType', 'module', 'not in', modules],
		['DocType', 'read_only', '=', 0], ['DocType', 'name', 'like', '%{0}%'.format(txt)]]

	doctypes = frappe.get_all('DocType', fields = ['`tabDocType`.`name`'], filters=filters,
		order_by = '`tabDocType`.`idx` desc', limit_start=start, limit_page_length=page_len, as_list=1, debug=1)

	custom_dt_filters = [['Custom Field', 'dt', 'like', '%{0}%'.format(txt)],
		['Custom Field', 'options', '=', 'User'], ['Custom Field', 'fieldtype', '=', 'Link']]

	custom_doctypes = frappe.get_all('Custom Field', fields = ['dt as name'],
		filters= custom_dt_filters, as_list=1)

	return doctypes + custom_doctypes

@frappe.whitelist()
def get_user_id(parent):
	data = frappe.get_all('DocField', fields = ['label', 'fieldname as value'],
		filters= {'options': 'User', 'fieldtype': 'Link', 'parent': parent}) or []

	data.extend(frappe.get_all('Custom Field', fields = ['label', 'fieldname as value'],
		filters= {'options': 'User', 'fieldtype': 'Link', 'dt': parent}))

	return data

def user_linked_with_permission_on_doctype(doc, user):
	if not doc.apply_user_permission_on:
		return True

	if not doc.user_id_field:
		frappe.throw(_('User Id Field is mandatory in the user type {0}')
			.format(frappe.bold(doc.name)))

	if frappe.db.get_value(doc.apply_user_permission_on,
		{doc.user_id_field: user}, 'name'):
		return True
	else:
		label = frappe.get_meta(doc.apply_user_permission_on).get_field(doc.user_id_field).label

		frappe.msgprint(_("To set the role {0} in the user {1}, kindly set the {2} field as {3} in one of the {4} record.")
			.format(frappe.bold(doc.role), frappe.bold(user), frappe.bold(label),
				frappe.bold(user), frappe.bold(doc.apply_user_permission_on)))

		return False

def apply_permissions_for_non_standard_user_type(doc, method=None):
	'''Create user permission for the non standard user type'''
	if not frappe.db.table_exists('User Type'):
		return

	user_types = frappe.cache().get_value('non_standard_user_types')

	if not user_types:
		user_types = get_non_standard_user_type_details()

	if not user_types:
		return

	for user_type, data in user_types.items():
		if (not doc.get(data[1]) or doc.doctype != data[0]):
			continue

		if frappe.get_cached_value('User', doc.get(data[1]), 'user_type') != user_type:
			return

		if (doc.get(data[1]) and (not doc._doc_before_save or doc.get(data[1]) != doc._doc_before_save.get(data[1])
			or not frappe.db.get_value('User Permission',
				{'user': doc.get(data[1]), 'allow': data[0], 'for_value': doc.name}, 'name'))):

			perm_data = frappe.db.get_value('User Permission',
				{'allow': doc.doctype, 'for_value': doc.name}, ['name', 'user'])

			if not perm_data:
				user_doc = frappe.get_cached_doc('User', doc.get(data[1]))
				user_doc.set_roles_and_modules_based_on_user_type()
				user_doc.update_children()
				add_user_permission(doc.doctype, doc.name, doc.get(data[1]))
			else:
				frappe.db.set_value('User Permission', perm_data[0], 'user', doc.get(data[1]))
<commit_msg>fix: pass distinct as argument in set_modules<commit_after># -*- coding: utf-8 -*-
# Copyright (c) 2021, Frappe Technologies and contributors
# For license information, please see license.txt

import frappe
from frappe import _
from frappe.utils import get_link_to_form
from frappe.config import get_modules_from_app
from frappe.permissions import add_permission, add_user_permission
from frappe.model.document import Document

class UserType(Document):
	def validate(self):
		self.set_modules()
		self.add_select_perm_doctypes()

	def on_update(self):
		if self.is_standard:
			return

		self.validate_document_type_limit()
		self.validate_role()
		self.add_role_permissions_for_user_doctypes()
		self.add_role_permissions_for_select_doctypes()
		self.add_role_permissions_for_file()
		self.update_users()
		get_non_standard_user_type_details()
		self.remove_permission_for_deleted_doctypes()

	def on_trash(self):
		if self.is_standard:
			frappe.throw(_('Standard user type {0} can not be deleted.')
				.format(frappe.bold(self.name)))

	def set_modules(self):
		if not self.user_doctypes:
			return

		modules = frappe.get_all('DocType',
								fields=['module'],
								filters={'name': ('in', [d.document_type for d in self.user_doctypes])},
								distinct=True
							)

		self.set('user_type_modules', [])
		for row in modules:
			self.append('user_type_modules', {
				'module': row.module
			})

	def validate_document_type_limit(self):
		limit = frappe.conf.get('user_type_doctype_limit', {}).get(frappe.scrub(self.name))

		if not limit and frappe.session.user != 'Administrator':
			frappe.throw(_('User does not have permission to create the new {0}')
				.format(frappe.bold(_('User Type'))), title=_('Permission Error'))

		if not limit:
			frappe.throw(_('The limit has not set for the user type {0} in the site config file.')
				.format(frappe.bold(self.name)), title=_('Set Limit'))

		if self.user_doctypes and len(self.user_doctypes) > limit:
			frappe.throw(_('The total number of user document types limit has been crossed.'),
				title=_('User Document Types Limit Exceeded'))

		custom_doctypes = [row.document_type for row in self.user_doctypes if row.is_custom]
		if custom_doctypes and len(custom_doctypes) > 3:
			frappe.throw(_('You can only set the 3 custom doctypes in the Document Types table.'),
				title=_('Custom Document Types Limit Exceeded'))

	def validate_role(self):
		if not self.role:
			frappe.throw(_("The field {0} is mandatory")
				.format(frappe.bold(_('Role'))))

		if not frappe.db.get_value('Role', self.role, 'is_custom'):
			frappe.throw(_("The role {0} should be a custom role.")
				.format(frappe.bold(get_link_to_form('Role', self.role))))

	def update_users(self):
		for row in frappe.get_all('User', filters = {'user_type': self.name}):
			user = frappe.get_cached_doc('User', row.name)
			self.update_roles_in_user(user)
			self.update_modules_in_user(user)
			user.update_children()

	def update_roles_in_user(self, user):
		user.set('roles', [])
		user.append('roles', {
			'role': self.role
		})

	def update_modules_in_user(self, user):
		block_modules = frappe.get_all('Module Def', fields = ['name as module'],
			filters={'name': ['not in', [d.module for d in self.user_type_modules]]})

		if block_modules:
			user.set('block_modules', block_modules)

	def add_role_permissions_for_user_doctypes(self):
		perms = ['read', 'write', 'create', 'submit', 'cancel', 'amend', 'delete']
		for row in self.user_doctypes:
			docperm = add_role_permissions(row.document_type, self.role)

			values = {perm:row.get(perm) or 0 for perm in perms}
			for perm in ['print', 'email', 'share']:
				values[perm] = 1

			frappe.db.set_value('Custom DocPerm', docperm, values)

	def add_select_perm_doctypes(self):
		if frappe.flags.ignore_select_perm:
			return

		self.select_doctypes = []

		select_doctypes = []
		user_doctypes = [row.document_type for row in self.user_doctypes]

		for doctype in user_doctypes:
			doc = frappe.get_meta(doctype)
			self.prepare_select_perm_doctypes(doc, user_doctypes, select_doctypes)

			for child_table in doc.get_table_fields():
				child_doc = frappe.get_meta(child_table.options)
				if not child_doc.istable:
					self.prepare_select_perm_doctypes(child_doc, user_doctypes, select_doctypes)

		if select_doctypes:
			select_doctypes = set(select_doctypes)
			for select_doctype in select_doctypes:
				self.append('select_doctypes', {
					'document_type': select_doctype
				})

	def prepare_select_perm_doctypes(self, doc, user_doctypes, select_doctypes):
		for field in doc.get_link_fields():
			if field.options not in user_doctypes:
				select_doctypes.append(field.options)

	def add_role_permissions_for_select_doctypes(self):
		for doctype in ['select_doctypes', 'custom_select_doctypes']:
			for row in self.get(doctype):
				docperm = add_role_permissions(row.document_type, self.role)
				frappe.db.set_value('Custom DocPerm', docperm,
					{'select': 1, 'read': 0, 'create': 0, 'write': 0})

	def add_role_permissions_for_file(self):
		docperm = add_role_permissions('File', self.role)
		frappe.db.set_value('Custom DocPerm', docperm,
			{'read': 1, 'create': 1, 'write': 1})

	def remove_permission_for_deleted_doctypes(self):
		doctypes = [d.document_type for d in self.user_doctypes]

		# Do not remove the doc permission for the file doctype
		doctypes.append('File')

		for doctype in ['select_doctypes', 'custom_select_doctypes']:
			for dt in self.get(doctype):
				doctypes.append(dt.document_type)

		for perm in frappe.get_all('Custom DocPerm',
			filters = {'role': self.role, 'parent': ['not in', doctypes]}):
			frappe.delete_doc('Custom DocPerm', perm.name)

def add_role_permissions(doctype, role):
	name = frappe.get_value('Custom DocPerm', dict(parent=doctype,
		role=role, permlevel=0))

	if not name:
		name = add_permission(doctype, role, 0)

	return name

def get_non_standard_user_type_details():
	user_types = frappe.get_all('User Type',
		fields=['apply_user_permission_on', 'name', 'user_id_field'],
		filters={'is_standard': 0})

	if user_types:
		user_type_details = {d.name: [d.apply_user_permission_on, d.user_id_field] for d in user_types}

		frappe.cache().set_value('non_standard_user_types', user_type_details)

		return user_type_details

@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_user_linked_doctypes(doctype, txt, searchfield, start, page_len, filters):
	modules = [d.get('module_name') for d in get_modules_from_app('frappe')]

	filters = [['DocField', 'options', '=', 'User'], ['DocType', 'is_submittable', '=', 0],
		['DocType', 'issingle', '=', 0], ['DocType', 'module', 'not in', modules],
		['DocType', 'read_only', '=', 0], ['DocType', 'name', 'like', '%{0}%'.format(txt)]]

	doctypes = frappe.get_all('DocType', fields = ['`tabDocType`.`name`'], filters=filters,
		order_by = '`tabDocType`.`idx` desc', limit_start=start, limit_page_length=page_len, as_list=1, debug=1)

	custom_dt_filters = [['Custom Field', 'dt', 'like', '%{0}%'.format(txt)],
		['Custom Field', 'options', '=', 'User'], ['Custom Field', 'fieldtype', '=', 'Link']]

	custom_doctypes = frappe.get_all('Custom Field', fields = ['dt as name'],
		filters= custom_dt_filters, as_list=1)

	return doctypes + custom_doctypes

@frappe.whitelist()
def get_user_id(parent):
	data = frappe.get_all('DocField', fields = ['label', 'fieldname as value'],
		filters= {'options': 'User', 'fieldtype': 'Link', 'parent': parent}) or []

	data.extend(frappe.get_all('Custom Field', fields = ['label', 'fieldname as value'],
		filters= {'options': 'User', 'fieldtype': 'Link', 'dt': parent}))

	return data

def user_linked_with_permission_on_doctype(doc, user):
	if not doc.apply_user_permission_on:
		return True

	if not doc.user_id_field:
		frappe.throw(_('User Id Field is mandatory in the user type {0}')
			.format(frappe.bold(doc.name)))

	if frappe.db.get_value(doc.apply_user_permission_on,
		{doc.user_id_field: user}, 'name'):
		return True
	else:
		label = frappe.get_meta(doc.apply_user_permission_on).get_field(doc.user_id_field).label

		frappe.msgprint(_("To set the role {0} in the user {1}, kindly set the {2} field as {3} in one of the {4} record.")
			.format(frappe.bold(doc.role), frappe.bold(user), frappe.bold(label),
				frappe.bold(user), frappe.bold(doc.apply_user_permission_on)))

		return False

def apply_permissions_for_non_standard_user_type(doc, method=None):
	'''Create user permission for the non standard user type'''
	if not frappe.db.table_exists('User Type'):
		return

	user_types = frappe.cache().get_value('non_standard_user_types')

	if not user_types:
		user_types = get_non_standard_user_type_details()

	if not user_types:
		return

	for user_type, data in user_types.items():
		if (not doc.get(data[1]) or doc.doctype != data[0]):
			continue

		if frappe.get_cached_value('User', doc.get(data[1]), 'user_type') != user_type:
			return

		if (doc.get(data[1]) and (not doc._doc_before_save or doc.get(data[1]) != doc._doc_before_save.get(data[1])
			or not frappe.db.get_value('User Permission',
				{'user': doc.get(data[1]), 'allow': data[0], 'for_value': doc.name}, 'name'))):

			perm_data = frappe.db.get_value('User Permission',
				{'allow': doc.doctype, 'for_value': doc.name}, ['name', 'user'])

			if not perm_data:
				user_doc = frappe.get_cached_doc('User', doc.get(data[1]))
				user_doc.set_roles_and_modules_based_on_user_type()
				user_doc.update_children()
				add_user_permission(doc.doctype, doc.name, doc.get(data[1]))
			else:
				frappe.db.set_value('User Permission', perm_data[0], 'user', doc.get(data[1]))
<|endoftext|>


<commit_before>from django.db import models, transaction, connection
from django.contrib.auth.models import User
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
from djmoney.models.fields import CurrencyField
from treebeard.ns_tree import NS_Node

class Account(NS_Node):
    INCOME = 1
    EXPENSE = 2
    ACCOUNT = 3
    TYPES = (
        (INCOME, _('income')),
        (EXPENSE, _('expense')),
        (ACCOUNT, _('account')),
    )

    title = models.CharField(
        verbose_name=_('title'),
        max_length=255,
        blank=True)

    type = models.IntegerField(
        verbose_name=_('type'),
        choices=TYPES)

    opened = models.DateField(
        verbose_name=_('date of open'),
        null=True, blank=True, auto_now_add=True
    )
    closed = models.DateField(
        verbose_name=_('date of close'),
        null=True, blank=True
    )

    credentials = models.TextField(
        verbose_name=_('credentials'),
        blank=True
    )

    @property
    def sorted_sheaves(self):
        """
        Property contains list of sheaves for the account, sorted buy currency
        in alphabetical order. Sheaf in base currency will be placed of first
        place if exists.
        :return:
        """
        result = list()
        for sheaf in self.sheaves.order_by('currency'):
            if sheaf.currency == settings.BASE_CURRENCY:
                result.insert(0, sheaf)
            else:
                result.append(sheaf)
        return result

    def recalculate_summary(self, atomic=True):
        def do():
            self.sheaves.all().delete()
            sql = 'SELECT SUM(`amount`) as amount, `currency` ' \
                  'FROM accountant_transaction ' \
                  'WHERE `account_id` = %s AND `approved` ' \
                  'GROUP BY `currency`;'
            with connection.cursor() as cursor:
                cursor.execute(sql, (self.id, ))
                result = cursor.fetchall()
            for amount, currency in result:
                Sheaf.objects.create(account=self,
                                     amount=amount,
                                     currency=currency).save()

        if atomic:
            with transaction.atomic():
                do()
        else:
            do()

    def tree_summary(self):
        '''
        method returns summary of all child accounts
        >>> account = Account.objects.get(pk=34)
        >>> account.tree_summary()
        {'EUR': Decimal('34'),
         'RUB': Decimal('45')}
        :return: dict with summary
        '''
        sql = 'SELECT SUM(`amount`) as amount, `currency`' \
              'FROM accountant_sheaf ' \
              'WHERE `account_id` IN (' \
              '   SELECT `id`' \
              '   FROM accountant_account' \
              '   WHERE `lft` > %s AND `rgt` < %s AND `tree_id` = %s' \
              ') GROUP BY `currency` ORDER BY `currency`;'
        with connection.cursor() as cursor:
            cursor.execute(sql, (self.lft, self.rgt, self.tree_id))
            result = cursor.fetchall()
        return {currency: amount for amount, currency in result}

    def __str__(self):
        return '{title} ({type})'.format(
            title=self.title,
            type=dict(self.TYPES)[self.type]
        )

    class Meta:
        verbose_name = _('account')
        verbose_name_plural = _('accounts')

class Sheaf(models.Model):
    account = models.ForeignKey(
        verbose_name=_('account'),
        to=Account,
        related_name='sheaves'
    )
    amount = models.DecimalField(
        verbose_name=_('sheaf'),
        max_digits=settings.MAX_DIGITS,
        decimal_places=settings.DECIMAL_PLACES
    )
    currency = CurrencyField(
        verbose_name=_('currency'),
        price_field='amount',
        default=settings.BASE_CURRENCY
    )

    def __str__(self):
        return '{amount} {currency} on {account}'.format(
            amount=self.amount,
            currency=self.currency,
            account=self.account
        )

    def __eq__(self, other):
        return type(other) == Sheaf and \
               self.account == other.account and \
               self.amount == other.amount and \
               self.currency == other.currency

    def __ne__(self, other):
        return not self.__eq__(other)

    class Meta:
        unique_together = ('account', 'currency')

class Invoice(models.Model):
    timestamp = models.DateTimeField(
        verbose_name=_('date and time')
    )

    comment = models.TextField(
        verbose_name=_('comment'),
        blank=True
    )

    user = models.ForeignKey(
        verbose_name=_('user'),
        to=User,
        blank=True,
        null=True
    )

    def verify(self):
        """
        method calculates sum of all transactions of the invoice and returns
        dictionary with currencies that has outstanding transactions.
        >>> invoice = Invoice.objects.get(pk=3)
        >>> invoice.verify()
        {'EUR': Decimal('-34')}
        :return: dictionary with currencies as keys and outstanding amounts
          as values. Empty dictionary can be thought as sign of verified
          invoice.
        """
        sql = 'SELECT SUM(`amount`) as amount, `currency` FROM `{}` ' \
              'WHERE `invoice_id` = %s GROUP BY `currency`;'
        with connection.cursor() as cursor:
            cursor.execute(sql.format(Transaction._meta.db_table), [self.id])
            result = cursor.fetchall()
        return {currency: amount for amount, currency in result if amount}

    @property
    def is_verified(self):
        return not bool(self.verify())

    def __str__(self):
        return _('Invoice from {timestamp}').format(
            timestamp=self.timestamp
        )

class Transaction(models.Model):
    date = models.DateField(
        verbose_name=_('date')
    )
    approved = models.BooleanField(
        verbose_name=_('approved'),
        default=True
    )

    account = models.ForeignKey(
        verbose_name=_('account'),
        to=Account,
        related_name='transactions'
    )

    amount = models.DecimalField(
        verbose_name=_('amount'),
        max_digits=settings.MAX_DIGITS,
        decimal_places=settings.DECIMAL_PLACES,
    )

    currency = CurrencyField(
        verbose_name=_('currency'),
        default=settings.BASE_CURRENCY,
        price_field=amount
    )

    invoice = models.ForeignKey(
        to=Invoice,
        related_name='transactions',
        blank=True, null=True, default=None
    )

    comment = models.TextField(
        verbose_name=_('comment'),
        blank=True
    )

    @transaction.atomic
    def save(self, *args, **kwargs):
        if not self.id:
            sheaf = self.account.sheaves.filter(currency=self.currency).first()
            if not sheaf:
                Sheaf.objects.create(account=self.account,
                                     currency=self.currency,
                                     amount=self.amount).save()
            else:
                sheaf.amount += self.amount
                sheaf.save()
            super(Transaction, self).save(*args, **kwargs)
        else:
            old_account = Transaction.objects.get(pk=self.pk).account
            super(Transaction, self).save(*args, **kwargs)
            self.account.recalculate_summary(atomic=False)
            if self.account != old_account:
                old_account.recalculate_summary(atomic=False)

class Document(models.Model):
    description = models.CharField(
        verbose_name=_('description'),
        max_length=1024,
        blank=True
    )

    invoice = models.ForeignKey(
        verbose_name=_('invoice'),
        to=Invoice,
        related_name='documents'
    )

    file = models.FileField(
        verbose_name=_('file with image')
    )
<commit_msg>first attempt to implement the task<commit_after>from django.db import models, transaction, connection
from django.contrib.auth.models import User
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
from djmoney.models.fields import CurrencyField
from treebeard.ns_tree import NS_Node

class Account(NS_Node):
    INCOME = 1
    EXPENSE = 2
    ACCOUNT = 3
    TYPES = (
        (INCOME, _('income')),
        (EXPENSE, _('expense')),
        (ACCOUNT, _('account')),
    )

    title = models.CharField(
        verbose_name=_('title'),
        max_length=255,
        blank=True)

    type = models.IntegerField(
        verbose_name=_('type'),
        choices=TYPES)

    opened = models.DateField(
        verbose_name=_('date of open'),
        null=True, blank=True, auto_now_add=True
    )
    closed = models.DateField(
        verbose_name=_('date of close'),
        null=True, blank=True
    )

    credentials = models.TextField(
        verbose_name=_('credentials'),
        blank=True
    )

    @property
    def sorted_sheaves(self):
        """
        Property contains list of sheaves for the account, sorted buy currency
        in alphabetical order. Sheaf in base currency will be placed of first
        place if exists.
        :return:
        """
        result = list()
        for sheaf in self.sheaves.order_by('currency'):
            if sheaf.currency == settings.BASE_CURRENCY:
                result.insert(0, sheaf)
            else:
                result.append(sheaf)
        return result

    def recalculate_summary(self, atomic=True):
        def do():
            self.sheaves.all().delete()
            sql = 'SELECT SUM(`amount`) as amount, `currency` ' \
                  'FROM accountant_transaction ' \
                  'WHERE `account_id` = %s AND `approved` ' \
                  'GROUP BY `currency`;'
            with connection.cursor() as cursor:
                cursor.execute(sql, (self.id, ))
                result = cursor.fetchall()
            for amount, currency in result:
                Sheaf.objects.create(account=self,
                                     amount=amount,
                                     currency=currency).save()

        if atomic:
            with transaction.atomic():
                do()
        else:
            do()

    def summary_at(self, date):
        """
        method returns dictionary with expected account summary
         for specified date (including that date)
        :param date: date of summary
        :return: dictionary with summary
        """
        sql = 'SELECT SUM(`amount`) as amount, `currency`' \
              'FROM accountant_transaction ' \
              'WHERE `account_id` IN (' \
              '   SELECT `id`' \
              '   FROM accountant_account' \
              '   WHERE `lft` > %s AND `rgt` < %s AND `tree_id` = %s' \
              ') AND date <= %s ' \
              'GROUP BY `currency` ORDER BY `currency`;'
        with connection.cursor() as cursor:
            cursor.execute(sql, (self.lft, self.rgt, self.tree_id))
            result = cursor.fetchall()
        return {currency: amount for amount, currency in result}

    def tree_summary(self):
        """
        method returns summary of all child accounts
        >>> account = Account.objects.get(pk=34)
        >>> account.tree_summary()
        {'EUR': Decimal('34'),
         'RUB': Decimal('45')}
        :return: dict with summary
        """
        sql = 'SELECT SUM(`amount`) as amount, `currency`' \
              'FROM accountant_sheaf ' \
              'WHERE `account_id` IN (' \
              '   SELECT `id`' \
              '   FROM accountant_account' \
              '   WHERE `lft` > %s AND `rgt` < %s AND `tree_id` = %s' \
              ') GROUP BY `currency` ORDER BY `currency`;'
        with connection.cursor() as cursor:
            cursor.execute(sql, (self.lft, self.rgt, self.tree_id))
            result = cursor.fetchall()
        return {currency: amount for amount, currency in result}

    def __str__(self):
        return '{title} ({type})'.format(
            title=self.title,
            type=dict(self.TYPES)[self.type]
        )

    class Meta:
        verbose_name = _('account')
        verbose_name_plural = _('accounts')

class Sheaf(models.Model):
    account = models.ForeignKey(
        verbose_name=_('account'),
        to=Account,
        related_name='sheaves'
    )
    amount = models.DecimalField(
        verbose_name=_('sheaf'),
        max_digits=settings.MAX_DIGITS,
        decimal_places=settings.DECIMAL_PLACES
    )
    currency = CurrencyField(
        verbose_name=_('currency'),
        price_field='amount',
        default=settings.BASE_CURRENCY
    )

    def __str__(self):
        return '{amount} {currency} on {account}'.format(
            amount=self.amount,
            currency=self.currency,
            account=self.account
        )

    def __eq__(self, other):
        return type(other) == Sheaf and \
               self.account == other.account and \
               self.amount == other.amount and \
               self.currency == other.currency

    def __ne__(self, other):
        return not self.__eq__(other)

    class Meta:
        unique_together = ('account', 'currency')

class Invoice(models.Model):
    timestamp = models.DateTimeField(
        verbose_name=_('date and time')
    )

    comment = models.TextField(
        verbose_name=_('comment'),
        blank=True
    )

    user = models.ForeignKey(
        verbose_name=_('user'),
        to=User,
        blank=True,
        null=True
    )

    def verify(self):
        """
        method calculates sum of all transactions of the invoice and returns
        dictionary with currencies that has outstanding transactions.
        >>> invoice = Invoice.objects.get(pk=3)
        >>> invoice.verify()
        {'EUR': Decimal('-34')}
        :return: dictionary with currencies as keys and outstanding amounts
          as values. Empty dictionary can be thought as sign of verified
          invoice.
        """
        sql = 'SELECT SUM(`amount`) as amount, `currency` FROM `{}` ' \
              'WHERE `invoice_id` = %s GROUP BY `currency`;'
        with connection.cursor() as cursor:
            cursor.execute(sql.format(Transaction._meta.db_table), [self.id])
            result = cursor.fetchall()
        return {currency: amount for amount, currency in result if amount}

    @property
    def is_verified(self):
        return not bool(self.verify())

    def __str__(self):
        return _('Invoice from {timestamp}').format(
            timestamp=self.timestamp
        )

class Transaction(models.Model):
    date = models.DateField(
        verbose_name=_('date')
    )
    approved = models.BooleanField(
        verbose_name=_('approved'),
        default=True
    )

    account = models.ForeignKey(
        verbose_name=_('account'),
        to=Account,
        related_name='transactions'
    )

    amount = models.DecimalField(
        verbose_name=_('amount'),
        max_digits=settings.MAX_DIGITS,
        decimal_places=settings.DECIMAL_PLACES,
    )

    currency = CurrencyField(
        verbose_name=_('currency'),
        default=settings.BASE_CURRENCY,
        price_field=amount
    )

    invoice = models.ForeignKey(
        to=Invoice,
        related_name='transactions',
        blank=True, null=True, default=None
    )

    comment = models.TextField(
        verbose_name=_('comment'),
        blank=True
    )

    @transaction.atomic
    def save(self, *args, **kwargs):
        if not self.id:
            sheaf = self.account.sheaves.filter(currency=self.currency).first()
            if not sheaf:
                Sheaf.objects.create(account=self.account,
                                     currency=self.currency,
                                     amount=self.amount).save()
            else:
                sheaf.amount += self.amount
                sheaf.save()
            super(Transaction, self).save(*args, **kwargs)
        else:
            old_account = Transaction.objects.get(pk=self.pk).account
            super(Transaction, self).save(*args, **kwargs)
            self.account.recalculate_summary(atomic=False)
            if self.account != old_account:
                old_account.recalculate_summary(atomic=False)

class Document(models.Model):
    description = models.CharField(
        verbose_name=_('description'),
        max_length=1024,
        blank=True
    )

    invoice = models.ForeignKey(
        verbose_name=_('invoice'),
        to=Invoice,
        related_name='documents'
    )

    file = models.FileField(
        verbose_name=_('file with image')
    )
<|endoftext|>


<commit_before>'''
Minimum Spanning Tree functionality for PyRate.

Contains functions to calculate MST using interferograms.

.. codeauthor:: Ben Davies
'''

from itertools import product
from numpy import array, nan, isnan, float32, empty

from pyrate.algorithm import get_all_epochs, master_slave_ids, ifg_date_lookup, get_epochs
from pyrate.algorithm import ifg_date_index_lookup

from pygraph.classes.graph import graph
from pygraph.algorithms.minmax import minimal_spanning_tree

# TODO: may need to implement memory saving row-by-row access
# TODO: document weighting by either Nan fraction OR variance

def _remove_root_node(mst):
    """
    Discard pygraph's root node from MST dict to conserve memory.
    """

    for k in mst.keys():
        if mst[k] is None:
            del mst[k]
    return mst

# TODO: this does not appear to be used anywhere
def default_mst(ifgs, noroot=True):
    '''
    Returns default MST dict for the given Ifgs. The MST is calculated using a
    weighting based on the number of incoherent cells in the phase band.

    :param noroot: True removes the PyGraph default root node from the result.
    '''

    edges = [(i.master, i.slave) for i in ifgs]
    epochs = master_slave_ids(get_all_epochs(ifgs)).keys()
    weights = [i.nan_fraction for i in ifgs]  # NB: other attrs for weights?

    g = _build_graph(epochs, edges, weights)
    mst = minimal_spanning_tree(g)
    return _remove_root_node(mst) if noroot else mst

def _build_graph(nodes, edges, weights, noroot=True):
    """
    Convenience graph builder function: returns a new graph obj.
    """

    g = graph()
    g.add_nodes(nodes)
    for edge, weight in zip(edges, weights):
        g.add_edge(edge, wt=weight)

    return g

def mst_matrix_ifgs_only(ifgs):
    '''
    Filter: returns array of independent ifgs from the pixel by pixel MST.

    The MSTs are stripped of connecting edge info, leaving just the ifgs.

    :param ifgs: sequence of Ifg objs
    :param epochs: an EpochList object derived from the ifgs
    '''

    result = empty(shape=ifgs[0].phase_data.shape, dtype=object)

    for y, x, mst in mst_matrix(ifgs):
        # mst is a dictionary of dates
        if hasattr(mst, 'iteritems'):
            ifg_sub = [ifg_date_lookup(ifgs, d) for d in mst.iteritems()]
            result[(y, x)] = tuple(ifg_sub)
        else:
            result[(y, x)] = mst  # usually NaN
    return result

def mst_matrix_ifg_indices(ifgs):
    '''
    Filter: returns array of independent ifg indices from the pixel by pixel MST.

    The MSTs are stripped of connecting edge info, leaving just the ifgs.
    ifgs: sequence of Ifg objs.

    :param epochs: an EpochList object derived from the ifgs
    '''

    result = empty(shape=ifgs[0].phase_data.shape, dtype=object)

    for y, x, mst in mst_matrix(ifgs):
        if hasattr(mst, 'iteritems'):
            ifg_sub = [ifg_date_index_lookup(ifgs, d) for d in mst.iteritems()]
            result[(y, x)] = tuple(ifg_sub)
        else:
            result[(y, x)] = mst  # usually NaN

    return result

def mst_matrix_as_array(ifgs):
    '''
    Filter: returns array of pixel by pixel MSTs.

    Each pixel contains an MST (with connecting edges etc).

    :param ifgs: sequence of Ifg objs
    :param epochs: an EpochList object derived from the ifgs
    '''

    mst_result = empty(shape=ifgs[0].phase_data.shape, dtype=object)

    for y, x, mst in mst_matrix(ifgs):
        mst_result[y, x] = mst

    return mst_result

# TODO: custom weighting could included with an additional 'weights' arg if some
# other weighting criterion is required later
def mst_matrix(ifgs):
    '''
    Generates/emits MST trees on a pixel-by-pixel basis for the given ifgs.
    :param ifgs: sequence of Ifg objs
    :param epochs: an EpochList object derived from the ifgs
    '''

    # make default MST to optimise result when no Ifg cells in a stack are nans
    epochs = get_epochs(ifgs)
    edges = [(i.master, i.slave) for i in ifgs]
    weights = [i.nan_fraction for i in ifgs]
    g = _build_graph(epochs.dates, edges, weights)
    dflt_mst = _remove_root_node(minimal_spanning_tree(g))

    # prepare source and dest data arrays
    # TODO: memory efficiencies can be achieved here with tiling
    data_stack = array([i.phase_data for i in ifgs], dtype=float32)

    # create MSTs for each pixel in the ifg data stack
    nifgs = len(ifgs)
    for y, x in product(xrange(i.nrows), xrange(i.ncols)):
        values = data_stack[:, y, x] # vertical stack of ifg values for a pixel
        nancount = sum(isnan(values))

        # optimisations: use pre-created results for all nans/no nans
        if nancount == 0:
            yield y, x, dflt_mst
            continue
        elif nancount == nifgs:
            yield y, x, nan
            continue

        # dynamically modify graph to reuse a single graph: this should avoid
        # repeatedly creating new graph objs & reduce RAM use
        for value, edge, weight in zip(values, edges, weights):
            if not isnan(value):
                if not g.has_edge(edge):
                    g.add_edge(edge, wt=weight)
            else:
                if g.has_edge(edge):
                    g.del_edge(edge)

        mst = _remove_root_node(minimal_spanning_tree(g))
        yield y, x, mst
<commit_msg> Improved _remove_root_node method.<commit_after>'''
Minimum Spanning Tree functionality for PyRate.

Contains functions to calculate MST using interferograms.

.. codeauthor:: Ben Davies
'''

from itertools import product
from numpy import array, nan, isnan, float32, empty

from pyrate.algorithm import get_all_epochs, master_slave_ids, ifg_date_lookup, get_epochs
from pyrate.algorithm import ifg_date_index_lookup

from pygraph.classes.graph import graph
from pygraph.algorithms.minmax import minimal_spanning_tree

# TODO: may need to implement memory saving row-by-row access
# TODO: document weighting by either Nan fraction OR variance

def _remove_root_node(mst):
    """
    Discard pygraph's root node from MST dict to conserve memory.
    """

    for k in mst:
        if mst[k] is None:
            del mst[k]
            break
    return mst

# TODO: this does not appear to be used anywhere
def default_mst(ifgs, noroot=True):
    '''
    Returns default MST dict for the given Ifgs. The MST is calculated using a
    weighting based on the number of incoherent cells in the phase band.

    :param noroot: True removes the PyGraph default root node from the result.
    '''

    edges = [(i.master, i.slave) for i in ifgs]
    epochs = master_slave_ids(get_all_epochs(ifgs)).keys()
    weights = [i.nan_fraction for i in ifgs]  # NB: other attrs for weights?

    g = _build_graph(epochs, edges, weights)
    mst = minimal_spanning_tree(g)
    return _remove_root_node(mst) if noroot else mst

def _build_graph(nodes, edges, weights, noroot=True):
    """
    Convenience graph builder function: returns a new graph obj.
    """

    g = graph()
    g.add_nodes(nodes)
    for edge, weight in zip(edges, weights):
        g.add_edge(edge, wt=weight)

    return g

def mst_matrix_ifgs_only(ifgs):
    '''
    Filter: returns array of independent ifgs from the pixel by pixel MST.

    The MSTs are stripped of connecting edge info, leaving just the ifgs.

    :param ifgs: sequence of Ifg objs
    :param epochs: an EpochList object derived from the ifgs
    '''

    result = empty(shape=ifgs[0].phase_data.shape, dtype=object)

    for y, x, mst in mst_matrix(ifgs):
        # mst is a dictionary of dates
        if hasattr(mst, 'iteritems'):
            ifg_sub = [ifg_date_lookup(ifgs, d) for d in mst.iteritems()]
            result[(y, x)] = tuple(ifg_sub)
        else:
            result[(y, x)] = mst  # usually NaN
    return result

def mst_matrix_ifg_indices(ifgs):
    '''
    Filter: returns array of independent ifg indices from the pixel by pixel MST.

    The MSTs are stripped of connecting edge info, leaving just the ifgs.
    ifgs: sequence of Ifg objs.

    :param epochs: an EpochList object derived from the ifgs
    '''

    result = empty(shape=ifgs[0].phase_data.shape, dtype=object)

    for y, x, mst in mst_matrix(ifgs):
        if hasattr(mst, 'iteritems'):
            ifg_sub = [ifg_date_index_lookup(ifgs, d) for d in mst.iteritems()]
            result[(y, x)] = tuple(ifg_sub)
        else:
            result[(y, x)] = mst  # usually NaN

    return result

def mst_matrix_as_array(ifgs):
    '''
    Filter: returns array of pixel by pixel MSTs.

    Each pixel contains an MST (with connecting edges etc).

    :param ifgs: sequence of Ifg objs
    :param epochs: an EpochList object derived from the ifgs
    '''

    mst_result = empty(shape=ifgs[0].phase_data.shape, dtype=object)

    for y, x, mst in mst_matrix(ifgs):
        mst_result[y, x] = mst

    return mst_result

# TODO: custom weighting could included with an additional 'weights' arg if some
# other weighting criterion is required later
def mst_matrix(ifgs):
    '''
    Generates/emits MST trees on a pixel-by-pixel basis for the given ifgs.
    :param ifgs: sequence of Ifg objs
    :param epochs: an EpochList object derived from the ifgs
    '''

    # make default MST to optimise result when no Ifg cells in a stack are nans
    epochs = get_epochs(ifgs)
    edges = [(i.master, i.slave) for i in ifgs]
    weights = [i.nan_fraction for i in ifgs]
    g = _build_graph(epochs.dates, edges, weights)

    # TODO: Investigate better algotithms for minimal spanning tree
    dflt_mst = _remove_root_node(minimal_spanning_tree(g))

    # prepare source and dest data arrays
    # TODO: memory efficiencies can be achieved here with tiling
    data_stack = array([i.phase_data for i in ifgs], dtype=float32)

    # create MSTs for each pixel in the ifg data stack
    nifgs = len(ifgs)
    for y, x in product(xrange(i.nrows), xrange(i.ncols)):
        values = data_stack[:, y, x] # vertical stack of ifg values for a pixel
        nancount = sum(isnan(values))

        # optimisations: use pre-created results for all nans/no nans
        if nancount == 0:
            yield y, x, dflt_mst
            continue
        elif nancount == nifgs:
            yield y, x, nan
            continue

        # dynamically modify graph to reuse a single graph: this should avoid
        # repeatedly creating new graph objs & reduce RAM use
        for value, edge, weight in zip(values, edges, weights):
            if not isnan(value):
                if not g.has_edge(edge):
                    g.add_edge(edge, wt=weight)
            else:
                if g.has_edge(edge):
                    g.del_edge(edge)

        mst = _remove_root_node(minimal_spanning_tree(g))
        yield y, x, mst
<|endoftext|>


<commit_before>import os
import shutil
import tempfile
import re
from pip import call_subprocess
from pip.util import display_path, rmtree
from pip.vcs import vcs, VersionControl
from pip.log import logger
from urllib import url2pathname
from urlparse import urlsplit, urlunsplit

class Git(VersionControl):
    name = 'git'
    dirname = '.git'
    repo_name = 'clone'
    schemes = ('git', 'git+http', 'git+https', 'git+ssh', 'git+git', 'git+file')
    bundle_file = 'git-clone.txt'
    guide = ('# This was a Git repo; to make it a repo again run:\n'
        'git init\ngit remote add origin %(url)s -f\ngit checkout %(rev)s\n')

    def __init__(self, url=None, *args, **kwargs):

        # Works around an apparent Git bug
        # (see http://article.gmane.org/gmane.comp.version-control.git/146500)
        if url:
            scheme, netloc, path, query, fragment = urlsplit(url)
            if scheme.endswith('file'):
                initial_slashes = path[:-len(path.lstrip('/'))]
                newpath = initial_slashes + url2pathname(path).replace('\\', '/').lstrip('/')
                url = urlunsplit((scheme, netloc, newpath, query, fragment))
                after_plus = scheme.find('+')+1
                url = scheme[:after_plus]+ urlunsplit((scheme[after_plus:], netloc, newpath, query, fragment))

        super(Git, self).__init__(url, *args, **kwargs)

    def parse_vcs_bundle_file(self, content):
        url = rev = None
        for line in content.splitlines():
            if not line.strip() or line.strip().startswith('#'):
                continue
            url_match = re.search(r'git\s*remote\s*add\s*origin(.*)\s*-f', line)
            if url_match:
                url = url_match.group(1).strip()
            rev_match = re.search(r'^git\s*checkout\s*-q\s*(.*)\s*', line)
            if rev_match:
                rev = rev_match.group(1).strip()
            if url and rev:
                return url, rev
        return None, None

    def export(self, location):
        """Export the Git repository at the url to the destination location"""
        temp_dir = tempfile.mkdtemp('-export', 'pip-')
        self.unpack(temp_dir)
        try:
            if not location.endswith('/'):
                location = location + '/'
            call_subprocess(
                [self.cmd, 'checkout-index', '-a', '-f', '--prefix', location],
                filter_stdout=self._filter, show_stdout=False, cwd=temp_dir)
        finally:
            rmtree(temp_dir)

    def check_rev_options(self, rev, dest, rev_options):
        """Check the revision options before checkout to compensate that tags
        and branches may need origin/ as a prefix.
        Returns the SHA1 of the branch or tag if found.
        """
        revisions = self.get_tag_revs(dest)
        revisions.update(self.get_branch_revs(dest))
        inverse_revisions = dict((v, k) for k, v in revisions.iteritems())
        # Check if rev is a branch name
        origin_rev = 'origin/%s' % rev
        if origin_rev in inverse_revisions:
            return [inverse_revisions[origin_rev]]
        elif rev in inverse_revisions:
            return [inverse_revisions[rev]]
        else:
            logger.warn("Could not find a tag or branch '%s', assuming commit." % rev)
            return rev_options

    def switch(self, dest, url, rev_options):
        call_subprocess(
            [self.cmd, 'config', 'remote.origin.url', url], cwd=dest)
        call_subprocess(
            [self.cmd, 'checkout', '-q'] + rev_options, cwd=dest)

    def update(self, dest, rev_options):
        # First fetch changes from the default remote
        call_subprocess([self.cmd, 'fetch', '-q'], cwd=dest)
        # Then reset to wanted revision (maby even origin/master)
        call_subprocess([self.cmd, 'reset', '--hard', '-q'] + rev_options, cwd=dest)

    def obtain(self, dest):
        url, rev = self.get_url_rev()
        if rev:
            rev_options = [rev]
            rev_display = ' (to %s)' % rev
        else:
            rev_options = ['origin/master']
            rev_display = ''
        if self.check_destination(dest, url, rev_options, rev_display):
            logger.notify('Cloning %s%s to %s' % (url, rev_display, display_path(dest)))
            call_subprocess([self.cmd, 'clone', '-q', url, dest])
            if rev:
                rev_options = self.check_rev_options(rev, dest, rev_options)
                # Only do a checkout if rev_options differs from HEAD
                if not self.get_revision(dest).startswith(rev_options[0]):
                    call_subprocess([self.cmd, 'checkout', '-q'] + rev_options, cwd=dest)

    def get_url(self, location):
        url = call_subprocess(
            [self.cmd, 'config', 'remote.origin.url'],
            show_stdout=False, cwd=location)
        return url.strip()

    def get_revision(self, location):
        current_rev = call_subprocess(
            [self.cmd, 'rev-parse', 'HEAD'], show_stdout=False, cwd=location)
        return current_rev.strip()

    def get_tag_revs(self, location):
        tags = call_subprocess(
            [self.cmd, 'tag', '-l'],
            show_stdout=False, raise_on_returncode=False, cwd=location)
        tag_revs = []
        for line in tags.splitlines():
            tag = line.strip()
            rev = call_subprocess(
                [self.cmd, 'rev-parse', tag], show_stdout=False, cwd=location)
            tag_revs.append((rev.strip(), tag))
        tag_revs = dict(tag_revs)
        return tag_revs

    def get_branch_revs(self, location):
        branches = call_subprocess(
            [self.cmd, 'branch', '-r'], show_stdout=False, cwd=location)
        branch_revs = []
        for line in branches.splitlines():
            line = line.split('->')[0].strip()
            branch = "".join([b for b in line.split() if b != '*'])
            rev = call_subprocess(
                [self.cmd, 'rev-parse', branch], show_stdout=False, cwd=location)
            branch_revs.append((rev.strip(), branch))
        branch_revs = dict(branch_revs)
        return branch_revs

    def get_src_requirement(self, dist, location, find_tags):
        repo = self.get_url(location)
        if not repo.lower().startswith('git:'):
            repo = 'git+' + repo
        egg_project_name = dist.egg_name().split('-', 1)[0]
        if not repo:
            return None
        current_rev = self.get_revision(location)
        tag_revs = self.get_tag_revs(location)
        branch_revs = self.get_branch_revs(location)

        if current_rev in tag_revs:
            # It's a tag
            full_egg_name = '%s-%s' % (egg_project_name, tag_revs[current_rev])
        elif (current_rev in branch_revs and
              branch_revs[current_rev] != 'origin/master'):
            # It's the head of a branch
            full_egg_name = '%s-%s' % (dist.egg_name(),
                                       branch_revs[current_rev].replace('origin/', ''))
        else:
            full_egg_name = '%s-dev' % dist.egg_name()

        return '%s@%s#egg=%s' % (repo, current_rev, full_egg_name)

    def get_url_rev(self):
        """
        Prefixes stub URLs like 'user@hostname:user/repo.git' with 'ssh://'.
        That's required because although they use SSH they sometimes doesn't
        work with a ssh:// scheme (e.g. Github). But we need a scheme for
        parsing. Hence we remove it again afterwards and return it as a stub.
        """
        if not '://' in self.url:
            assert not 'file:' in self.url
            self.url = self.url.replace('git+', 'git+ssh://')
            url, rev = super(Git, self).get_url_rev()
            url = url.replace('ssh://', '')
        else:
            url, rev = super(Git, self).get_url_rev()

        return url, rev

vcs.register(Git)
<commit_msg>Added check_rev_options in update() to add missing origin, thanks hedberg.<commit_after>import os
import shutil
import tempfile
import re
from pip import call_subprocess
from pip.util import display_path, rmtree
from pip.vcs import vcs, VersionControl
from pip.log import logger
from urllib import url2pathname
from urlparse import urlsplit, urlunsplit

class Git(VersionControl):
    name = 'git'
    dirname = '.git'
    repo_name = 'clone'
    schemes = ('git', 'git+http', 'git+https', 'git+ssh', 'git+git', 'git+file')
    bundle_file = 'git-clone.txt'
    guide = ('# This was a Git repo; to make it a repo again run:\n'
        'git init\ngit remote add origin %(url)s -f\ngit checkout %(rev)s\n')

    def __init__(self, url=None, *args, **kwargs):

        # Works around an apparent Git bug
        # (see http://article.gmane.org/gmane.comp.version-control.git/146500)
        if url:
            scheme, netloc, path, query, fragment = urlsplit(url)
            if scheme.endswith('file'):
                initial_slashes = path[:-len(path.lstrip('/'))]
                newpath = initial_slashes + url2pathname(path).replace('\\', '/').lstrip('/')
                url = urlunsplit((scheme, netloc, newpath, query, fragment))
                after_plus = scheme.find('+')+1
                url = scheme[:after_plus]+ urlunsplit((scheme[after_plus:], netloc, newpath, query, fragment))

        super(Git, self).__init__(url, *args, **kwargs)

    def parse_vcs_bundle_file(self, content):
        url = rev = None
        for line in content.splitlines():
            if not line.strip() or line.strip().startswith('#'):
                continue
            url_match = re.search(r'git\s*remote\s*add\s*origin(.*)\s*-f', line)
            if url_match:
                url = url_match.group(1).strip()
            rev_match = re.search(r'^git\s*checkout\s*-q\s*(.*)\s*', line)
            if rev_match:
                rev = rev_match.group(1).strip()
            if url and rev:
                return url, rev
        return None, None

    def export(self, location):
        """Export the Git repository at the url to the destination location"""
        temp_dir = tempfile.mkdtemp('-export', 'pip-')
        self.unpack(temp_dir)
        try:
            if not location.endswith('/'):
                location = location + '/'
            call_subprocess(
                [self.cmd, 'checkout-index', '-a', '-f', '--prefix', location],
                filter_stdout=self._filter, show_stdout=False, cwd=temp_dir)
        finally:
            rmtree(temp_dir)

    def check_rev_options(self, rev, dest, rev_options):
        """Check the revision options before checkout to compensate that tags
        and branches may need origin/ as a prefix.
        Returns the SHA1 of the branch or tag if found.
        """
        revisions = self.get_tag_revs(dest)
        revisions.update(self.get_branch_revs(dest))
        inverse_revisions = dict((v, k) for k, v in revisions.iteritems())
        # Check if rev is a branch name
        origin_rev = 'origin/%s' % rev
        if origin_rev in inverse_revisions:
            # a remote tag or branch name
            return [inverse_revisions[origin_rev]]
        elif rev in inverse_revisions:
            # a local tag or branch name
            return [inverse_revisions[rev]]
        else:
            logger.warn("Could not find a tag or branch '%s', assuming commit." % rev)
            return rev_options

    def switch(self, dest, url, rev_options):
        call_subprocess(
            [self.cmd, 'config', 'remote.origin.url', url], cwd=dest)
        call_subprocess(
            [self.cmd, 'checkout', '-q'] + rev_options, cwd=dest)

    def update(self, dest, rev_options):
        # First fetch changes from the default remote
        call_subprocess([self.cmd, 'fetch', '-q'], cwd=dest)
        # Then reset to wanted revision (maby even origin/master)
        if rev_options:
            rev_options = self.check_rev_options(rev_options[0], dest, rev_options)
        call_subprocess([self.cmd, 'reset', '--hard', '-q'] + rev_options, cwd=dest)

    def obtain(self, dest):
        url, rev = self.get_url_rev()
        if rev:
            rev_options = [rev]
            rev_display = ' (to %s)' % rev
        else:
            rev_options = ['origin/master']
            rev_display = ''
        if self.check_destination(dest, url, rev_options, rev_display):
            logger.notify('Cloning %s%s to %s' % (url, rev_display, display_path(dest)))
            call_subprocess([self.cmd, 'clone', '-q', url, dest])
            if rev:
                rev_options = self.check_rev_options(rev, dest, rev_options)
                # Only do a checkout if rev_options differs from HEAD
                if not self.get_revision(dest).startswith(rev_options[0]):
                    call_subprocess([self.cmd, 'checkout', '-q'] + rev_options, cwd=dest)

    def get_url(self, location):
        url = call_subprocess(
            [self.cmd, 'config', 'remote.origin.url'],
            show_stdout=False, cwd=location)
        return url.strip()

    def get_revision(self, location):
        current_rev = call_subprocess(
            [self.cmd, 'rev-parse', 'HEAD'], show_stdout=False, cwd=location)
        return current_rev.strip()

    def get_tag_revs(self, location):
        tags = call_subprocess(
            [self.cmd, 'tag', '-l'],
            show_stdout=False, raise_on_returncode=False, cwd=location)
        tag_revs = []
        for line in tags.splitlines():
            tag = line.strip()
            rev = call_subprocess(
                [self.cmd, 'rev-parse', tag], show_stdout=False, cwd=location)
            tag_revs.append((rev.strip(), tag))
        tag_revs = dict(tag_revs)
        return tag_revs

    def get_branch_revs(self, location):
        branches = call_subprocess(
            [self.cmd, 'branch', '-r'], show_stdout=False, cwd=location)
        branch_revs = []
        for line in branches.splitlines():
            line = line.split('->')[0].strip()
            branch = "".join([b for b in line.split() if b != '*'])
            rev = call_subprocess(
                [self.cmd, 'rev-parse', branch], show_stdout=False, cwd=location)
            branch_revs.append((rev.strip(), branch))
        branch_revs = dict(branch_revs)
        return branch_revs

    def get_src_requirement(self, dist, location, find_tags):
        repo = self.get_url(location)
        if not repo.lower().startswith('git:'):
            repo = 'git+' + repo
        egg_project_name = dist.egg_name().split('-', 1)[0]
        if not repo:
            return None
        current_rev = self.get_revision(location)
        tag_revs = self.get_tag_revs(location)
        branch_revs = self.get_branch_revs(location)

        if current_rev in tag_revs:
            # It's a tag
            full_egg_name = '%s-%s' % (egg_project_name, tag_revs[current_rev])
        elif (current_rev in branch_revs and
              branch_revs[current_rev] != 'origin/master'):
            # It's the head of a branch
            full_egg_name = '%s-%s' % (dist.egg_name(),
                                       branch_revs[current_rev].replace('origin/', ''))
        else:
            full_egg_name = '%s-dev' % dist.egg_name()

        return '%s@%s#egg=%s' % (repo, current_rev, full_egg_name)

    def get_url_rev(self):
        """
        Prefixes stub URLs like 'user@hostname:user/repo.git' with 'ssh://'.
        That's required because although they use SSH they sometimes doesn't
        work with a ssh:// scheme (e.g. Github). But we need a scheme for
        parsing. Hence we remove it again afterwards and return it as a stub.
        """
        if not '://' in self.url:
            assert not 'file:' in self.url
            self.url = self.url.replace('git+', 'git+ssh://')
            url, rev = super(Git, self).get_url_rev()
            url = url.replace('ssh://', '')
        else:
            url, rev = super(Git, self).get_url_rev()

        return url, rev

vcs.register(Git)
<|endoftext|>