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

from __future__ import absolute_import
from __future__ import division

from datetime import datetime
import re
from urllib2 import URLError

from bs4 import BeautifulSoup
import mechanize

class ParivahanTimeOutException(Exception):

    def __int__(self, message):
        super(ParivahanTimeOutException, self).__init__(message)

class ParivahanException(Exception):

    def __int__(self, message):
        super(ParivahanException, self).__init__(message)

def get_parivahan_data(registration_no, request_timeout=10):

    clean = lambda x: x.strip('\n\t\r: ')
    join_with_underscore = lambda x: "_".join(clean(x).split())

    reg_match = re.match(r"([A-Za-z]{2}[0-9]{1,2}[A-Za-z]{1,3})([0-9]{1,4})", registration_no)
    if not reg_match:
        raise ParivahanException('Registration number is not valid')

    br = mechanize.Browser()
    br.set_handle_refresh(False)
    try:
        br.open('https://parivahan.gov.in/rcdlstatus/vahan/rcstatus.xhtml',
                timeout=request_timeout)
    except URLError as e:
        raise ParivahanTimeOutException(e.message)

    br.select_form('convVeh_Form')
    br.form['convVeh_Form:tf_reg_no1'] = reg_match.group(1).upper()
    br.form['convVeh_Form:tf_reg_no2'] = reg_match.group(2)

    res = br.submit()
    soup = BeautifulSoup(res.get_data(), 'lxml')
    table_rows = soup.table.find_all('tr')

    data = {}
    for row in table_rows:
        table_columns = row.find_all('td')
        for i in xrange(0, len(table_columns), 2):
            try:
                if table_columns[i] and table_columns[i+1]:
                    key = join_with_underscore(table_columns[i].text).lower()
                    val = clean(table_columns[i+1].text)

                    if key == 'registration_date':
                        try:
                            val = datetime.strptime(val, '%d-%b-%Y')
                        except Exception as e:
                            print "Exception {} while parsing registration date: {}".format(e.message, val)
                            val = None

                    data[key] = val
            except Exception as e:
                print e.message
                continue

    return data

def is_vehicle_stolen(registration_no, request_timeout=10):

    # is_valid() checks if vehicle_type is a substring of vehicle types found on samanvay website
    # vehicle_class() gets a list of html select option and filters them who are `is_valid`
    # sorry for making it too cryptic. Didn't want to add one more function
    is_valid = lambda x: x.text.strip() in vehicle_type
    vehicle_class = lambda xx: (filter(is_valid, xx) or [None])[0]

    registration_details = get_parivahan_data(registration_no)
    owner = registration_details.get('owner_name')
    chassis_no = registration_details.get('chasi_no')
    engine_no = registration_details.get('engine_no')
    vehicle_type = registration_details.get('vehicle_class')

    if None in (chassis_no, engine_no, vehicle_type, owner):
        raise Exception('Could not fetch required registration details from parivahan.')

    br = mechanize.Browser()
    try:
        res = br.open('http://164.100.44.112/vahansamanvay/Internetquery.aspx',
                      timeout=request_timeout)
    except URLError as e:
        raise ParivahanTimeOutException(e.message)

    try:
        #filter vehicle class
        vehicle_type = vehicle_class(BeautifulSoup(res.get_data(),
                                                   'lxml').find('select',
                                                                id='ddVehTypeName').find_all('option'))
        if vehicle_type is None:
            vehicle_type = '287   -5'  # this is for the option `CAR`.
            # Vehicle class data from parivhan is not very reliable and does not correspond
            # to the ones in samanvay dropdown list
        else:
            vehicle_type = vehicle_type.get('value')

    except Exception as e:
        print "Couldn't determine vehicle type from the registration data. Error: {}".format(e.message)

    br.select_form(nr=0)
    br.form['txtAppName'] = owner
    br.form['ddVehTypeName'] = [vehicle_type]
    br.form['txtRegistration'] = registration_no
    br.form['txtChasis'] = chassis_no
    br.form['txtEngine'] = engine_no

    br.submit()

    # mechanize will take care of sending session cookies.
    res = br.open('http://164.100.44.112/vahansamanvay/InternetQueryOutput.aspx')
    soup = BeautifulSoup(res.get_data(), 'lxml')
    panel = soup.find('div', id='Panel1')
    tables = soup.find_all('table')
    if not tables or len(tables) < 2:
        raise Exception('Unexpected page structure.')

    table = tables[1]
    tr = (table.find_all('tr') or [None])[-1]
    if tr is None:
        raise Exception('Unexpected page structure.')

    text = tr.find('td').text
    if text and 'has not been reported as stolen by police' in text:
        return False, re.sub(r"[\r\n\t ]+", " ", str(panel))
    else:
        # don't know what comes in the text for stolen vehicle
        return True, re.sub(r"[\r\n\t ]+", " ", str(panel))

if __name__ == '__main__':
    print get_parivahan_data('KA05JH6941')<commit_msg>* added captcha handling with parivahan.<commit_after># -*- coding: utf-8 -*-

from __future__ import absolute_import
from __future__ import division

from datetime import datetime
import re
from urllib2 import URLError

import base64
from bs4 import BeautifulSoup
from captcha import CaptchaClient
import mechanize
import requests
import time

class ParivahanTimeOutException(Exception):

    def __int__(self, message):
        super(ParivahanTimeOutException, self).__init__(message)

class ParivahanException(Exception):

    def __int__(self, message):
        super(ParivahanException, self).__init__(message)

def _get_captcha(html, captcha_key):
    soup = BeautifulSoup(html, 'lxml')
    table = soup.find('table', **{'class': 'vahan-captcha'})
    img = table.find('img')
    captcha_url = 'https://parivahan.gov.in{}'.format(img.get('src'))
    captcha_img = requests.get(captcha_url).content
    captcha_client = CaptchaClient(captcha_key)

    captcha_id = captcha_client.submit_captcha_b64(base64.b64encode(captcha_img))
    time.sleep(6)
    return captcha_client.get_solved_captcha(captcha_id)

def get_parivahan_data(registration_no, captcha_api_key, request_timeout=10):

    clean = lambda x: x.strip('\n\t\r: ')
    join_with_underscore = lambda x: "_".join(clean(x).split())

    reg_match = re.match(r"([A-Za-z]{2}[0-9]{1,2}[A-Za-z]{1,3})([0-9]{1,4})", registration_no)
    if not reg_match:
        raise ParivahanException('Registration number is not valid')

    br = mechanize.Browser()
    br.set_handle_refresh(False)
    try:
        res = br.open('https://parivahan.gov.in/rcdlstatus/vahan/rcstatus.xhtml',
                      timeout=request_timeout)
    except URLError as e:
        raise ParivahanTimeOutException(e.message)

    captcha = _get_captcha(res.get_data(), captcha_api_key)

    br.select_form('rc_Form')
    br.form['rc_Form:tf_reg_no1'] = reg_match.group(1).upper()
    br.form['rc_Form:tf_reg_no2'] = reg_match.group(2)
    br.form['rc_Form:j_idt26:CaptchaID'] = captcha

    res = br.submit()

    soup = BeautifulSoup(res.get_data(), 'lxml')

    data = {}
    div = soup.find('div', id='rc_Form:rcPanel')
    table = div.find('table')
    table_rows = table.find_all('tr')

    for row in table_rows:
        table_columns = row.find_all('td')
        for i in xrange(0, len(table_columns), 2):
            try:
                if table_columns[i] and table_columns[i+1]:
                    key = join_with_underscore(table_columns[i].text).lower()
                    val = clean(table_columns[i+1].text)

                    if key == 'registration_date':
                        try:
                            val = datetime.strptime(val, '%d-%b-%Y')
                        except Exception as e:
                            print "Exception {} while parsing registration date: {}".format(e.message, val)
                            val = None

                    data[key] = val
            except Exception as e:
                print e.message
                continue

    return data

def is_vehicle_stolen(registration_no, captcha_api_key, request_timeout=10):

    # is_valid() checks if vehicle_type is a substring of vehicle types found on samanvay website
    # vehicle_class() gets a list of html select option and filters them who are `is_valid`
    # sorry for making it too cryptic. Didn't want to add one more function
    is_valid = lambda x: x.text.strip() in vehicle_type
    vehicle_class = lambda xx: (filter(is_valid, xx) or [None])[0]

    registration_details = get_parivahan_data(registration_no, captcha_api_key)
    owner = registration_details.get('owner_name')
    chassis_no = registration_details.get('chasi_no')
    engine_no = registration_details.get('engine_no')
    vehicle_type = registration_details.get('vehicle_class')

    if None in (chassis_no, engine_no, vehicle_type, owner):
        raise Exception('Could not fetch required registration details from parivahan.')

    br = mechanize.Browser()
    try:
        res = br.open('http://164.100.44.112/vahansamanvay/Internetquery.aspx',
                      timeout=request_timeout)
    except URLError as e:
        raise ParivahanTimeOutException(e.message)

    try:
        #filter vehicle class
        vehicle_type = vehicle_class(BeautifulSoup(res.get_data(),
                                                   'lxml').find('select',
                                                                id='ddVehTypeName').find_all('option'))
        if vehicle_type is None:
            vehicle_type = '287   -5'  # this is for the option `CAR`.
            # Vehicle class data from parivhan is not very reliable and does not correspond
            # to the ones in samanvay dropdown list
        else:
            vehicle_type = vehicle_type.get('value')

    except Exception as e:
        print "Couldn't determine vehicle type from the registration data. Error: {}".format(e.message)

    br.select_form(nr=0)
    br.form['txtAppName'] = owner
    br.form['ddVehTypeName'] = [vehicle_type]
    br.form['txtRegistration'] = registration_no
    br.form['txtChasis'] = chassis_no
    br.form['txtEngine'] = engine_no

    br.submit()

    # mechanize will take care of sending session cookies.
    res = br.open('http://164.100.44.112/vahansamanvay/InternetQueryOutput.aspx')
    soup = BeautifulSoup(res.get_data(), 'lxml')
    panel = soup.find('div', id='Panel1')
    tables = soup.find_all('table')
    if not tables or len(tables) < 2:
        raise Exception('Unexpected page structure.')

    table = tables[1]
    tr = (table.find_all('tr') or [None])[-1]
    if tr is None:
        raise Exception('Unexpected page structure.')

    text = tr.find('td').text
    if text and 'has not been reported as stolen by police' in text:
        return False, re.sub(r"[\r\n\t ]+", " ", str(panel))
    else:
        # don't know what comes in the text for stolen vehicle
        return True, re.sub(r"[\r\n\t ]+", " ", str(panel))

if __name__ == '__main__':
    print get_parivahan_data('KA05JH6941')<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
"""
Custom serializer fields for nested translations.
"""
from __future__ import unicode_literals
from django.core.exceptions import ImproperlyConfigured
from rest_framework import serializers
from rest_framework.compat import OrderedDict
from parler_rest.utils import create_translated_fields_serializer

class TranslatedFieldsField(serializers.Field):
    """
    Exposing all translated fields for a TranslatableModel in REST style.
    """
    default_error_messages = dict(serializers.Field.default_error_messages, **{
        'invalid': "Input is not a valid dict",
    })

    def __init__(self, *args, **kwargs):
        """
        Receive custom serializer class and model.
        """
        self.serializer_class = kwargs.pop('serializer_class', None)
        self.shared_model = kwargs.pop('shared_model', None)
        super(TranslatedFieldsField, self).__init__(*args, **kwargs)

    def bind(self, field_name, parent):
        """
        Create translation serializer dynamically.

        Takes translatable model class (shared_model) from parent serializer and it
        may create a serializer class on the fly if no custom class was specified.
        """
        super(TranslatedFieldsField, self).bind(field_name, parent)
        # Expect 1-on-1 for now.
        related_name = field_name

        # This could all be done in __init__(), but by moving the code here,
        # it's possible to auto-detect the parent model.
        if self.shared_model is not None and self.serializer_class is not None:
            return

        # Fill in the blanks
        if self.serializer_class is None:
            # Auto detect parent model
            if self.shared_model is None:
                self.shared_model = parent.opts.model

            # Create serializer based on shared model.
            translated_model = self.shared_model._parler_meta[related_name]
            self.serializer_class = create_translated_fields_serializer(
                self.shared_model, related_name=related_name,
                meta={'fields': translated_model.get_translated_fields()}
            )
        else:
            self.shared_model = self.serializer_class.Meta.model

    def to_representation(self, value):
        """
        Serialize translated fields.

        Simply iterate over available translations and, for each language,
        delegate serialization logic to the translation model serializer.
        """
        if value is None:
            return

        # Only need one serializer to create the native objects
        serializer = self.serializer_class()

        # Don't need to have a 'language_code', it will be split up already,
        # so this should avoid redundant output.
        if 'language_code' in serializer.fields:
            raise ImproperlyConfigured("Serializer may not have a 'language_code' field")

        # Split into a dictionary per language
        result = serializers.OrderedDict()
        for translation in value.all():  # value = translations related manager
            result[translation.language_code] = serializer.to_representation(translation)

        return result

    def to_internal_value(self, data):
        """
        Deserialize data from translations fields.

        For each received language, delegate validation logic to
        the translation model serializer.
        """
        if data is None:
            return

        if not isinstance(data, dict):
            self.fail('invalid')

        result, errors = {}, {}
        for lang_code, model_fields in data.items():
            serializer = self.serializer_class(data=model_fields)
            if serializer.is_valid():
                result[lang_code] = serializer.data
            else:
                errors[lang_code] = serializer.errors

        if errors:
            raise serializers.ValidationError(errors)
        return result

class TranslatedField(serializers.ReadOnlyField):
    """
    Read-only field to expose a single object property in all it's languages.
    """

    def get_attribute(self, instance):
        # Instead of fetching the attribute with getattr() (that proxies to the Parler TranslatableField),
        # read the translation model directly to fetch all languages, and combine that into a dict.
        model = instance._parler_meta.get_model_by_field(self.source)  # This already validates the fields existance
        extension = instance._parler_meta[model]
        translations = getattr(instance, extension.rel_name)

        # Split into a dictionary per language
        value = OrderedDict()
        for translation in translations.all():  # Allow prefetch_related() to do it's work
            value[translation.language_code] = getattr(translation, self.source)
        return value

    def to_representation(self, value):
        return value
<commit_msg>Allow 'source' argument on TranslatedFieldsField<commit_after># -*- coding: utf-8 -*-
"""
Custom serializer fields for nested translations.
"""
from __future__ import unicode_literals
from django.core.exceptions import ImproperlyConfigured
from rest_framework import serializers
from rest_framework.compat import OrderedDict
from parler_rest.utils import create_translated_fields_serializer

class TranslatedFieldsField(serializers.Field):
    """
    Exposing all translated fields for a TranslatableModel in REST style.
    """
    default_error_messages = dict(serializers.Field.default_error_messages, **{
        'invalid': "Input is not a valid dict",
    })

    def __init__(self, *args, **kwargs):
        """
        Receive custom serializer class and model.
        """
        self.serializer_class = kwargs.pop('serializer_class', None)
        self.shared_model = kwargs.pop('shared_model', None)
        super(TranslatedFieldsField, self).__init__(*args, **kwargs)

    def bind(self, field_name, parent):
        """
        Create translation serializer dynamically.

        Takes translatable model class (shared_model) from parent serializer and it
        may create a serializer class on the fly if no custom class was specified.
        """
        super(TranslatedFieldsField, self).bind(field_name, parent)

        # Allow using source as alias,
        # but it should not be a dotted path for now
        related_name = self.source

        # This could all be done in __init__(), but by moving the code here,
        # it's possible to auto-detect the parent model.
        if self.shared_model is not None and self.serializer_class is not None:
            return

        # Fill in the blanks
        if self.serializer_class is None:
            # Auto detect parent model
            if self.shared_model is None:
                self.shared_model = parent.opts.model

            # Create serializer based on shared model.
            translated_model = self.shared_model._parler_meta[related_name]
            self.serializer_class = create_translated_fields_serializer(
                self.shared_model, related_name=related_name,
                meta={'fields': translated_model.get_translated_fields()}
            )
        else:
            self.shared_model = self.serializer_class.Meta.model

    def to_representation(self, value):
        """
        Serialize translated fields.

        Simply iterate over available translations and, for each language,
        delegate serialization logic to the translation model serializer.
        """
        if value is None:
            return

        # Only need one serializer to create the native objects
        serializer = self.serializer_class()

        # Don't need to have a 'language_code', it will be split up already,
        # so this should avoid redundant output.
        if 'language_code' in serializer.fields:
            raise ImproperlyConfigured("Serializer may not have a 'language_code' field")

        # Split into a dictionary per language
        result = serializers.OrderedDict()
        for translation in value.all():  # value = translations related manager
            result[translation.language_code] = serializer.to_representation(translation)

        return result

    def to_internal_value(self, data):
        """
        Deserialize data from translations fields.

        For each received language, delegate validation logic to
        the translation model serializer.
        """
        if data is None:
            return

        if not isinstance(data, dict):
            self.fail('invalid')

        result, errors = {}, {}
        for lang_code, model_fields in data.items():
            serializer = self.serializer_class(data=model_fields)
            if serializer.is_valid():
                result[lang_code] = serializer.data
            else:
                errors[lang_code] = serializer.errors

        if errors:
            raise serializers.ValidationError(errors)
        return result

class TranslatedField(serializers.ReadOnlyField):
    """
    Read-only field to expose a single object property in all it's languages.
    """

    def get_attribute(self, instance):
        # Instead of fetching the attribute with getattr() (that proxies to the Parler TranslatableField),
        # read the translation model directly to fetch all languages, and combine that into a dict.
        model = instance._parler_meta.get_model_by_field(self.source)  # This already validates the fields existance
        extension = instance._parler_meta[model]
        translations = getattr(instance, extension.rel_name)

        # Split into a dictionary per language
        value = OrderedDict()
        for translation in translations.all():  # Allow prefetch_related() to do it's work
            value[translation.language_code] = getattr(translation, self.source)
        return value

    def to_representation(self, value):
        return value
<|endoftext|>


<commit_before>##########################################################################
#
#  Copyright (c) 2007, Image Engine Design Inc. All rights reserved.
#
#  Redistribution and use in source and binary forms, with or without
#  modification, are permitted provided that the following conditions are
#  met:
#
#     * Redistributions of source code must retain the above copyright
#       notice, this list of conditions and the following disclaimer.
#
#     * Redistributions in binary form must reproduce the above copyright
#       notice, this list of conditions and the following disclaimer in the
#       documentation and/or other materials provided with the distribution.
#
#     * Neither the name of Image Engine Design nor the names of any
#       other contributors to this software may be used to endorse or
#       promote products derived from this software without specific prior
#       written permission.
#
#  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
#  IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
#  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
#  PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
#  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
#  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
#  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
#  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
#  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
#  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
#  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
##########################################################################

import unittest

import VersionControl
VersionControl.setVersion( "IECore", "2" ) ## \todo need to get version from build config somehow, or have the coreGL module initialise the core module somehow
from IECore import *

from IECoreGL import *
init( False )

class TestShader( unittest.TestCase ) :

	def testConstructor( self ) :

		self.assertRaises( RuntimeError, Shader, "i don't think i'm valid", "me neither" )

		vertexSource = """
		void main()
		{
			gl_Position = ftransform();
		}
		"""

		fragmentSource = """
		void main()
		{
			gl_FragColor = vec4( 1, 0.5, 0.25, 1 );
		}
		"""

		Shader( vertexSource, fragmentSource )

	def testParameters( self ) :

		fragmentSource = """
		uniform bool boolParm;
		uniform int intParm;
		uniform float floatParm;

		uniform bvec2 bvec2Parm;
		uniform bvec3 bvec3Parm;
		// uniform ivec4 bvec4Parm; // we have no suitable datatype for specifying this in IECore

		uniform ivec2 ivec2Parm;
		uniform ivec3 ivec3Parm;
		// uniform ivec4 ivec4Parm; // we have no suitable datatype for specifying this in IECore

		uniform vec2 vec2Parm;
		uniform vec3 vec3Parm;
		uniform vec4 vec4Parm;

		uniform sampler2D s2D;

		uniform struct {
			int i;
			float f;
		} s;

		uniform mat3 mat3Parm;
		uniform mat4 mat4Parm;

		void main()
		{
			float x = vec4Parm.r + vec3Parm.g + vec2Parm.y + floatParm + float( intParm ) + float( boolParm );
			float xx = float( ivec2Parm.x ) + float( ivec3Parm.y ) + float( bvec2Parm.x ) + float( bvec3Parm.y );
			float xxx = float( s.i ) + s.f + texture2D( s2D, vec2Parm );
			vec4 p = mat4Parm * gl_FragCoord;
			vec3 pp = mat3Parm * gl_FragCoord.xyz;
			gl_FragColor = vec4( x + xx + xxx + p.x + pp.x, gl_Color.g, 0.5, 1 );
		}
		"""

		s = Shader( "", fragmentSource )
		self.assert_( s==s )

		expectedParameterNamesAndTypes = {
			"boolParm" : TypeId.BoolData,
			"intParm" : TypeId.IntData,
			"floatParm" : TypeId.FloatData,
			"bvec2Parm" : TypeId.V2iData,
			"bvec3Parm" : TypeId.V3iData,
			"ivec2Parm" : TypeId.V2iData,
			"ivec3Parm" : TypeId.V3iData,
			"vec2Parm" : TypeId.V2fData,
			"vec3Parm" : TypeId.V3fData,
			"vec4Parm" : TypeId.Color4fData,
			"s.f" : TypeId.FloatData,
			"s.i" : TypeId.IntData,
			"s2D" : Texture.staticTypeId(),
			"mat3Parm" : TypeId.M33fData,
			"mat4Parm" : TypeId.M44fData,
		}

		parameterNames = s.parameterNames()
		self.assertEqual( len( parameterNames ), len( expectedParameterNamesAndTypes ) )
		for n in expectedParameterNamesAndTypes.keys() :
			self.assert_( n in parameterNames )
			self.assert_( s.hasParameter( n ) )
			self.assert_( not s.hasParameter( n + "VeryUnlikelySuffix" ) )
			self.assertEqual( s.parameterType( n ), expectedParameterNamesAndTypes[n] )

		expectedNamesAndValues = {
			"boolParm" : BoolData( 0 ),
			"intParm" : IntData( 0 ),
			"floatParm" : FloatData( 0 ),
			"bvec2Parm" : V2iData( V2i( 0 ) ),
			"ivec2Parm" : V2iData( V2i( 0 ) ),
			"vec2Parm" : V2fData( V2f( 0 ) ),
			"bvec3Parm" : V3iData( V3i( 0 ) ),
			"ivec3Parm" : V3iData( V3i( 0 ) ),
			"vec3Parm" : V3fData( V3f( 0 ) ),
			"vec4Parm" : Color4fData( Color4f( 0 ) ),
		}
		for name, value in expectedNamesAndValues.items() :
			self.assertEqual( s.getParameter( name ), value )

		# must bind a shader before setting parameters
		s.bind()

		s.setParameter( "intParm", IntData( 1 ) )
		self.assertEqual( s.getParameter( "intParm" ), IntData( 1 ) )

		s.setParameter( "boolParm", IntData( 2 ) )
		self.assertEqual( s.getParameter( "boolParm" ), BoolData( 1 ) )

		s.setParameter( "boolParm", BoolData( False ) )
		self.assertEqual( s.getParameter( "boolParm" ), BoolData( False ) )

		s.setParameter( "boolParm", BoolData( True ) )
		self.assertEqual( s.getParameter( "boolParm" ), BoolData( True ) )

		s.setParameter( "boolParm", BoolData( 2 ) )
		self.assertEqual( s.getParameter( "boolParm" ), BoolData( 1 ) )

		s.setParameter( "floatParm", FloatData( 1 ) )
		self.assertEqual( s.getParameter( "floatParm" ), FloatData( 1 ) )

		s.setParameter( "bvec2Parm", V2iData( V2i( 0, 1 ) ) )
		self.assertEqual( s.getParameter( "bvec2Parm" ), V2iData( V2i( 0, 1 ) ) )

		s.setParameter( "bvec3Parm", V3iData( V3i( 1 ) ) )
		self.assertEqual( s.getParameter( "bvec3Parm" ), V3iData( V3i( 1 ) ) )

		s.setParameter( "ivec2Parm", V2iData( V2i( 1, 2 ) ) )
		self.assertEqual( s.getParameter( "ivec2Parm" ), V2iData( V2i( 1, 2 ) ) )

		s.setParameter( "ivec3Parm", V3iData( V3i( 1 ) ) )
		self.assertEqual( s.getParameter( "ivec3Parm" ), V3iData( V3i( 1 ) ) )

		s.setParameter( "vec2Parm", V2fData( V2f( 1, 2 ) ) )
		self.assertEqual( s.getParameter( "vec2Parm" ), V2fData( V2f( 1, 2 ) ) )

		s.setParameter( "vec3Parm", Color3fData( Color3f( 1 ) ) )
		self.assertEqual( s.getParameter( "vec3Parm" ), V3fData( V3f( 1 ) ) )

		s.setParameter( "vec4Parm", Color4fData( Color4f( 1 ) ) )
		self.assertEqual( s.getParameter( "vec4Parm" ), Color4fData( Color4f( 1 ) ) )

		s.setParameter( "s.i", IntData( 1 ) )
		self.assertEqual( s.getParameter( "s.i" ), IntData( 1 ) )

		s.setParameter( "s.f", FloatData( 1 ) )
		self.assertEqual( s.getParameter( "s.f" ), FloatData( 1 ) )

		s.setParameter( "mat3Parm", M33fData( M33f.createTranslated( V2f( 1, 2 ) ) ) )
		self.assertEqual( s.getParameter( "mat3Parm" ), M33fData( M33f.createTranslated( V2f( 1, 2 ) ) ) )

		s.setParameter( "mat4Parm", M44fData( M44f.createTranslated( V3f( 1, 2, 3 ) ) ) )
		self.assertEqual( s.getParameter( "mat4Parm" ), M44fData( M44f.createTranslated( V3f( 1, 2, 3 ) ) ) )

		# check that setting invalid values throws an Exception
		self.assertRaises( Exception, s.setParameter, "iDontExist", FloatData( 2 ) )
		self.assertRaises( Exception, s.setParameter, "boolParm", FloatData( 2 ) )
		self.assertRaises( Exception, s.setParameter, "intParm", FloatData( 2 ) )
		self.assertRaises( Exception, s.setParameter, "floatParm", IntData( 2 ) )
		self.assertRaises( Exception, s.setParameter, "ivec2Parm", V3iData( V3i( 2 ) ) )
		self.assertRaises( Exception, s.setParameter, "ivec3Parm", V3fData( V3f( 2 ) ) )
		self.assertRaises( Exception, s.setParameter, "vec2Parm", V3fData( V3f( 2 ) ) )
		self.assertRaises( Exception, s.setParameter, "vec3Parm", V2fData( V2f( 2 ) ) )
		self.assertRaises( Exception, s.setParameter, "vec4Parm", V3fData( V3f( 2 ) ) )
		self.assertRaises( Exception, s.setParameter, "mat4Parm", V3fData( V3f( 2 ) ) )
		self.assertRaises( Exception, s.setParameter, "mat3Parm", V3fData( V3f( 2 ) ) )

		# check that both booldata is valid for bools
		self.assert_( s.valueValid( "boolParm", BoolData( True ) ) )

	def testRendererCall( self ) :

		## \todo We need image output from the renderer so we don't have to look at
		# it in a window.

		r = Renderer()
		r.setOption( "gl:mode", StringData( "deferred" ) )
		r.setOption( "gl:searchPath:shader", StringData( "test/shaders" ) )
		r.setOption( "gl:searchPath:texture", StringData( "test/images" ) )
		r.worldBegin()

		# we have to make this here so that the shaders that get made are made in the
		# correct GL context. My understanding is that all shaders should work in all
		# GL contexts in the address space, but that doesn't seem to be the case.
		#w = SceneViewer( "scene", r.scene() )

		r.concatTransform( M44f.createTranslated( V3f( 0, 0, -5 ) ) )

		r.attributeBegin()
		r.setAttribute( "color", Color3fData( Color3f( 1, 0, 1 ) ) )
		r.shader( "surface", "grey", { "greyValue" : FloatData( 0.5 ) } )
		r.geometry( "sphere", {}, {} )
		r.attributeEnd()

		r.attributeBegin()
		r.concatTransform( M44f.createTranslated( V3f( -1, 0, 0 ) ) )
		r.shader( "surface", "color", { "colorValue" : Color3fData( Color3f( 1, 0.5, 0.25 ) ) } )
		r.geometry( "sphere", {}, {} )
		r.attributeEnd()

		r.attributeBegin()
		r.concatTransform( M44f.createTranslated( V3f( 1, 0, 0 ) ) )
		r.shader( "surface", "image", { "texture" : StringData( "colorBarsH512x512.exr" ) } )
		r.geometry( "sphere", {}, {} )
		r.attributeEnd()

		r.attributeBegin()
		vertexSource = """
		void main()
		{
			gl_Position = ftransform() + vec4( 0, 1, 0, 0 );
		}
		"""

		fragmentSource = """
		void main()
		{
			gl_FragColor = vec4( 1, 0, 0, 1 );
		}
		"""

		r.shader( "surface", "redTranslated", { "gl:vertexSource" : StringData( vertexSource ), "gl:fragmentSource" : StringData( fragmentSource ) } )
		r.geometry( "sphere", {}, {} )
		r.attributeEnd()

		r.worldEnd()

		#w.start()

if __name__ == "__main__":
    unittest.main()
<commit_msg>Test that vertex attribute thingummies don't show up as parameters.<commit_after>##########################################################################
#
#  Copyright (c) 2007, Image Engine Design Inc. All rights reserved.
#
#  Redistribution and use in source and binary forms, with or without
#  modification, are permitted provided that the following conditions are
#  met:
#
#     * Redistributions of source code must retain the above copyright
#       notice, this list of conditions and the following disclaimer.
#
#     * Redistributions in binary form must reproduce the above copyright
#       notice, this list of conditions and the following disclaimer in the
#       documentation and/or other materials provided with the distribution.
#
#     * Neither the name of Image Engine Design nor the names of any
#       other contributors to this software may be used to endorse or
#       promote products derived from this software without specific prior
#       written permission.
#
#  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
#  IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
#  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
#  PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
#  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
#  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
#  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
#  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
#  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
#  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
#  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
##########################################################################

import unittest

import VersionControl
VersionControl.setVersion( "IECore", "2" ) ## \todo need to get version from build config somehow, or have the coreGL module initialise the core module somehow
from IECore import *

from IECoreGL import *
init( False )

class TestShader( unittest.TestCase ) :

	def testConstructor( self ) :

		self.assertRaises( RuntimeError, Shader, "i don't think i'm valid", "me neither" )

		vertexSource = """
		void main()
		{
			gl_Position = ftransform();
		}
		"""

		fragmentSource = """
		void main()
		{
			gl_FragColor = vec4( 1, 0.5, 0.25, 1 );
		}
		"""

		Shader( vertexSource, fragmentSource )

	def testParameters( self ) :

		vertexSource = """
		attribute float floatAttrib;
		varying float varyingFloatParm;
		void main()
		{
			gl_Position = ftransform();
			varyingFloatParm = floatAttrib * gl_Position.x;
		}
		"""

		fragmentSource = """
		uniform bool boolParm;
		uniform int intParm;
		uniform float floatParm;

		uniform bvec2 bvec2Parm;
		uniform bvec3 bvec3Parm;
		// uniform ivec4 bvec4Parm; // we have no suitable datatype for specifying this in IECore

		uniform ivec2 ivec2Parm;
		uniform ivec3 ivec3Parm;
		// uniform ivec4 ivec4Parm; // we have no suitable datatype for specifying this in IECore

		uniform vec2 vec2Parm;
		uniform vec3 vec3Parm;
		uniform vec4 vec4Parm;

		uniform sampler2D s2D;

		uniform struct {
			int i;
			float f;
		} s;

		uniform mat3 mat3Parm;
		uniform mat4 mat4Parm;

		varying float varyingFloatParm;

		void main()
		{
			float x = vec4Parm.r + vec3Parm.g + vec2Parm.y + floatParm + float( intParm ) + float( boolParm );
			float xx = float( ivec2Parm.x ) + float( ivec3Parm.y ) + float( bvec2Parm.x ) + float( bvec3Parm.y );
			float xxx = float( s.i ) + s.f + texture2D( s2D, vec2Parm );
			vec4 p = mat4Parm * gl_FragCoord;
			vec3 pp = mat3Parm * gl_FragCoord.xyz;
			gl_FragColor = vec4( x + xx + xxx + p.x + pp.x, gl_Color.g, varyingFloatParm, 1 );
		}
		"""

		s = Shader( vertexSource, fragmentSource )
		self.assert_( s==s )

		expectedParameterNamesAndTypes = {
			"boolParm" : TypeId.BoolData,
			"intParm" : TypeId.IntData,
			"floatParm" : TypeId.FloatData,
			"bvec2Parm" : TypeId.V2iData,
			"bvec3Parm" : TypeId.V3iData,
			"ivec2Parm" : TypeId.V2iData,
			"ivec3Parm" : TypeId.V3iData,
			"vec2Parm" : TypeId.V2fData,
			"vec3Parm" : TypeId.V3fData,
			"vec4Parm" : TypeId.Color4fData,
			"s.f" : TypeId.FloatData,
			"s.i" : TypeId.IntData,
			"s2D" : Texture.staticTypeId(),
			"mat3Parm" : TypeId.M33fData,
			"mat4Parm" : TypeId.M44fData,
		}

		parameterNames = s.parameterNames()
		self.assertEqual( len( parameterNames ), len( expectedParameterNamesAndTypes ) )
		for n in expectedParameterNamesAndTypes.keys() :
			self.assert_( n in parameterNames )
			self.assert_( s.hasParameter( n ) )
			self.assert_( not s.hasParameter( n + "VeryUnlikelySuffix" ) )
			self.assertEqual( s.parameterType( n ), expectedParameterNamesAndTypes[n] )

		expectedNamesAndValues = {
			"boolParm" : BoolData( 0 ),
			"intParm" : IntData( 0 ),
			"floatParm" : FloatData( 0 ),
			"bvec2Parm" : V2iData( V2i( 0 ) ),
			"ivec2Parm" : V2iData( V2i( 0 ) ),
			"vec2Parm" : V2fData( V2f( 0 ) ),
			"bvec3Parm" : V3iData( V3i( 0 ) ),
			"ivec3Parm" : V3iData( V3i( 0 ) ),
			"vec3Parm" : V3fData( V3f( 0 ) ),
			"vec4Parm" : Color4fData( Color4f( 0 ) ),
		}
		for name, value in expectedNamesAndValues.items() :
			self.assertEqual( s.getParameter( name ), value )

		# must bind a shader before setting parameters
		s.bind()

		s.setParameter( "intParm", IntData( 1 ) )
		self.assertEqual( s.getParameter( "intParm" ), IntData( 1 ) )

		s.setParameter( "boolParm", IntData( 2 ) )
		self.assertEqual( s.getParameter( "boolParm" ), BoolData( 1 ) )

		s.setParameter( "boolParm", BoolData( False ) )
		self.assertEqual( s.getParameter( "boolParm" ), BoolData( False ) )

		s.setParameter( "boolParm", BoolData( True ) )
		self.assertEqual( s.getParameter( "boolParm" ), BoolData( True ) )

		s.setParameter( "boolParm", BoolData( 2 ) )
		self.assertEqual( s.getParameter( "boolParm" ), BoolData( 1 ) )

		s.setParameter( "floatParm", FloatData( 1 ) )
		self.assertEqual( s.getParameter( "floatParm" ), FloatData( 1 ) )

		s.setParameter( "bvec2Parm", V2iData( V2i( 0, 1 ) ) )
		self.assertEqual( s.getParameter( "bvec2Parm" ), V2iData( V2i( 0, 1 ) ) )

		s.setParameter( "bvec3Parm", V3iData( V3i( 1 ) ) )
		self.assertEqual( s.getParameter( "bvec3Parm" ), V3iData( V3i( 1 ) ) )

		s.setParameter( "ivec2Parm", V2iData( V2i( 1, 2 ) ) )
		self.assertEqual( s.getParameter( "ivec2Parm" ), V2iData( V2i( 1, 2 ) ) )

		s.setParameter( "ivec3Parm", V3iData( V3i( 1 ) ) )
		self.assertEqual( s.getParameter( "ivec3Parm" ), V3iData( V3i( 1 ) ) )

		s.setParameter( "vec2Parm", V2fData( V2f( 1, 2 ) ) )
		self.assertEqual( s.getParameter( "vec2Parm" ), V2fData( V2f( 1, 2 ) ) )

		s.setParameter( "vec3Parm", Color3fData( Color3f( 1 ) ) )
		self.assertEqual( s.getParameter( "vec3Parm" ), V3fData( V3f( 1 ) ) )

		s.setParameter( "vec4Parm", Color4fData( Color4f( 1 ) ) )
		self.assertEqual( s.getParameter( "vec4Parm" ), Color4fData( Color4f( 1 ) ) )

		s.setParameter( "s.i", IntData( 1 ) )
		self.assertEqual( s.getParameter( "s.i" ), IntData( 1 ) )

		s.setParameter( "s.f", FloatData( 1 ) )
		self.assertEqual( s.getParameter( "s.f" ), FloatData( 1 ) )

		s.setParameter( "mat3Parm", M33fData( M33f.createTranslated( V2f( 1, 2 ) ) ) )
		self.assertEqual( s.getParameter( "mat3Parm" ), M33fData( M33f.createTranslated( V2f( 1, 2 ) ) ) )

		s.setParameter( "mat4Parm", M44fData( M44f.createTranslated( V3f( 1, 2, 3 ) ) ) )
		self.assertEqual( s.getParameter( "mat4Parm" ), M44fData( M44f.createTranslated( V3f( 1, 2, 3 ) ) ) )

		# check that setting invalid values throws an Exception
		self.assertRaises( Exception, s.setParameter, "iDontExist", FloatData( 2 ) )
		self.assertRaises( Exception, s.setParameter, "boolParm", FloatData( 2 ) )
		self.assertRaises( Exception, s.setParameter, "intParm", FloatData( 2 ) )
		self.assertRaises( Exception, s.setParameter, "floatParm", IntData( 2 ) )
		self.assertRaises( Exception, s.setParameter, "ivec2Parm", V3iData( V3i( 2 ) ) )
		self.assertRaises( Exception, s.setParameter, "ivec3Parm", V3fData( V3f( 2 ) ) )
		self.assertRaises( Exception, s.setParameter, "vec2Parm", V3fData( V3f( 2 ) ) )
		self.assertRaises( Exception, s.setParameter, "vec3Parm", V2fData( V2f( 2 ) ) )
		self.assertRaises( Exception, s.setParameter, "vec4Parm", V3fData( V3f( 2 ) ) )
		self.assertRaises( Exception, s.setParameter, "mat4Parm", V3fData( V3f( 2 ) ) )
		self.assertRaises( Exception, s.setParameter, "mat3Parm", V3fData( V3f( 2 ) ) )

		# check that both booldata is valid for bools
		self.assert_( s.valueValid( "boolParm", BoolData( True ) ) )

	def testRendererCall( self ) :

		## \todo We need image output from the renderer so we don't have to look at
		# it in a window.

		r = Renderer()
		r.setOption( "gl:mode", StringData( "deferred" ) )
		r.setOption( "gl:searchPath:shader", StringData( "test/shaders" ) )
		r.setOption( "gl:searchPath:texture", StringData( "test/images" ) )
		r.worldBegin()

		# we have to make this here so that the shaders that get made are made in the
		# correct GL context. My understanding is that all shaders should work in all
		# GL contexts in the address space, but that doesn't seem to be the case.
		#w = SceneViewer( "scene", r.scene() )

		r.concatTransform( M44f.createTranslated( V3f( 0, 0, -5 ) ) )

		r.attributeBegin()
		r.setAttribute( "color", Color3fData( Color3f( 1, 0, 1 ) ) )
		r.shader( "surface", "grey", { "greyValue" : FloatData( 0.5 ) } )
		r.geometry( "sphere", {}, {} )
		r.attributeEnd()

		r.attributeBegin()
		r.concatTransform( M44f.createTranslated( V3f( -1, 0, 0 ) ) )
		r.shader( "surface", "color", { "colorValue" : Color3fData( Color3f( 1, 0.5, 0.25 ) ) } )
		r.geometry( "sphere", {}, {} )
		r.attributeEnd()

		r.attributeBegin()
		r.concatTransform( M44f.createTranslated( V3f( 1, 0, 0 ) ) )
		r.shader( "surface", "image", { "texture" : StringData( "colorBarsH512x512.exr" ) } )
		r.geometry( "sphere", {}, {} )
		r.attributeEnd()

		r.attributeBegin()
		vertexSource = """
		void main()
		{
			gl_Position = ftransform() + vec4( 0, 1, 0, 0 );
		}
		"""

		fragmentSource = """
		void main()
		{
			gl_FragColor = vec4( 1, 0, 0, 1 );
		}
		"""

		r.shader( "surface", "redTranslated", { "gl:vertexSource" : StringData( vertexSource ), "gl:fragmentSource" : StringData( fragmentSource ) } )
		r.geometry( "sphere", {}, {} )
		r.attributeEnd()

		r.worldEnd()

		#w.start()

if __name__ == "__main__":
    unittest.main()
<|endoftext|>


<commit_before>from __future__ import annotations

import ast
from dataclasses import replace
from math import atan2

import gaphas
from gaphas.aspect.connector import ConnectionSink
from gaphas.aspect.connector import Connector as ConnectorAspect
from gaphas.connector import Handle
from gaphas.geometry import Rectangle, distance_rectangle_point
from gaphas.solver.constraint import BaseConstraint

from gaphor.core.modeling.diagram import Diagram
from gaphor.core.modeling.event import RevertibeEvent
from gaphor.core.modeling.presentation import Presentation, S
from gaphor.core.modeling.properties import attribute
from gaphor.core.styling import Style, merge_styles
from gaphor.diagram.text import TextAlign, text_point_at_line

class Named:
    """Marker for any NamedElement presentations."""

class Classified(Named):
    """Marker for Classifier presentations."""

def from_package_str(item):
    """Display name space info when it is different, then diagram's or parent's
    namespace."""
    subject = item.subject
    diagram = item.diagram

    if not (subject and diagram):
        return False

    namespace = subject.namespace
    parent = item.parent

    # if there is a parent (i.e. interaction)
    if parent and parent.subject and parent.subject.namespace is not namespace:
        return False

    return f"(from {namespace.name})" if namespace is not item.diagram.namespace else ""

def postload_connect(item: gaphas.Item, handle: gaphas.Handle, target: gaphas.Item):
    """Helper function: when loading a model, handles should be connected as
    part of the `postload` step.

    This function finds a suitable spot on the `target` item to connect
    the handle to.
    """
    connector = ConnectorAspect(item, handle, item.diagram.connections)
    sink = ConnectionSink(target, distance=1e4)
    connector.connect(sink)

class HandlePositionEvent(RevertibeEvent):

    requires_transaction = False

    def __init__(self, element, index, old_value):
        super().__init__(element)
        self.index = index
        self.old_value = old_value

    def revert(self, target):
        target.handles()[self.index].pos = self.old_value
        target.request_update()

class HandlePositionUpdate:
    def watch_handle(self, handle):
        handle.pos.add_handler(self._on_handle_position_update)

    def remove_watch_handle(self, handle):
        handle.pos.remove_handler(self._on_handle_position_update)

    def _on_handle_position_update(self, position, old):
        for index, handle in enumerate(self.handles()):  # type: ignore[attr-defined]
            if handle.pos is position:
                break
        else:
            return
        self.handle(HandlePositionEvent(self, index, old))  # type: ignore[attr-defined]

# Note: the official documentation is using the terms "Shape" and "Edge" for element and line.

class ElementPresentation(gaphas.Element, HandlePositionUpdate, Presentation[S]):
    """Presentation for Gaphas Element (box-like) items.

    To create a shape (boxes, text), assign a shape to `self.shape`. If
    the shape can change, for example, because styling needs to change,
    implement the method `update_shapes()` and set self.shape there.
    """

    _port_sides = ("top", "right", "bottom", "left")

    def __init__(self, diagram: Diagram, id=None, shape=None, width=100, height=50):
        super().__init__(connections=diagram.connections, diagram=diagram, id=id, width=width, height=height)  # type: ignore[call-arg]
        self._shape = shape
        for handle in self.handles():
            self.watch_handle(handle)

        diagram.connections.add_constraint(
            self, MinimalValueConstraint(self.min_width, width)
        )
        diagram.connections.add_constraint(
            self, MinimalValueConstraint(self.min_height, height)
        )

    def port_side(self, port):
        return self._port_sides[self._ports.index(port)]

    def _set_shape(self, shape):
        self._shape = shape
        self.request_update()

    shape = property(lambda s: s._shape, _set_shape)

    def update_shapes(self, event=None):
        """Updating the shape configuration, e.g. when extra elements have to
        be drawn or when styling changes."""

    def pre_update(self, context):
        if not self.shape:
            self.update_shapes()
        if self.shape:
            self.min_width, self.min_height = self.shape.size(context)

    def post_update(self, context):
        pass

    def draw(self, context):
        x, y = self.handles()[0].pos
        cairo = context.cairo
        cairo.translate(x, y)
        self._shape.draw(
            context,
            Rectangle(0, 0, self.width, self.height),
        )

    def save(self, save_func):
        save_func("matrix", tuple(self.matrix))
        save_func("width", self.width)
        save_func("height", self.height)
        super().save(save_func)

    def load(self, name, value):
        if name == "matrix":
            self.matrix.set(*ast.literal_eval(value))
        elif name == "width":
            self.width = ast.literal_eval(value)
        elif name == "height":
            self.height = ast.literal_eval(value)
        else:
            super().load(name, value)

    def postload(self):
        super().postload()
        self.update_shapes()

class LinePresentation(gaphas.Line, HandlePositionUpdate, Presentation[S]):
    def __init__(
        self,
        diagram: Diagram,
        id=None,
        style: Style = {},
        shape_head=None,
        shape_middle=None,
        shape_tail=None,
    ):
        super().__init__(connections=diagram.connections, diagram=diagram, id=id)  # type: ignore[call-arg]

        self.style = style
        self.shape_head = shape_head
        self.shape_middle = shape_middle
        self.shape_tail = shape_tail

        self.fuzziness = 2
        self._shape_head_rect = None
        self._shape_middle_rect = None
        self._shape_tail_rect = None
        self.watch("orthogonal", self._on_orthogonal).watch(
            "horizontal", self._on_horizontal
        )
        self.watch_handle(self.head)
        self.watch_handle(self.tail)

    head = property(lambda self: self._handles[0])
    tail = property(lambda self: self._handles[-1])

    orthogonal: attribute[int] = attribute("orthogonal", int, 0)
    horizontal: attribute[int] = attribute("horizontal", int, 0)

    @property
    def middle_shape_size(self) -> Rectangle:
        return self._shape_middle_rect

    def insert_handle(self, index: int, handle: Handle) -> None:
        super().insert_handle(index, handle)
        self.watch_handle(handle)

    def remove_handle(self, handle: Handle) -> None:
        self.remove_watch_handle(handle)
        super().remove_handle(handle)

    def pre_update(self, context):
        pass

    def post_update(self, context):
        def shape_bounds(shape, align):
            if shape:
                size = shape.size(context)
                x, y = text_point_at_line(points, size, align)
                return Rectangle(x, y, *size)

        points = [h.pos for h in self.handles()]
        self._shape_head_rect = shape_bounds(self.shape_head, TextAlign.LEFT)
        self._shape_middle_rect = shape_bounds(self.shape_middle, TextAlign.CENTER)
        self._shape_tail_rect = shape_bounds(self.shape_tail, TextAlign.RIGHT)

    def point(self, x, y):
        """Given a point (x, y) return the distance to the diagram item."""
        d0 = super().point(x, y)
        ds = [
            distance_rectangle_point(shape, (x, y))
            for shape in (
                self._shape_head_rect,
                self._shape_middle_rect,
                self._shape_tail_rect,
            )
            if shape
        ]
        return min(d0, *ds) if ds else d0

    def draw(self, context):
        def draw_line_end(end_handle, second_handle, draw):
            pos, p1 = end_handle.pos, second_handle.pos
            angle = atan2(p1.y - pos.y, p1.x - pos.x)
            cr = context.cairo
            cr.save()
            try:
                cr.translate(*pos)
                cr.rotate(angle)
                draw(context)
            finally:
                cr.restore()

        style = merge_styles(context.style, self.style)
        context = replace(context, style=style)

        cr = context.cairo
        cr.set_line_width(style["line-width"])
        cr.set_dash(style.get("dash-style", ()), 0)
        stroke = style["color"]
        if stroke:
            cr.set_source_rgba(*stroke)

        handles = self._handles
        draw_line_end(handles[0], handles[1], self.draw_head)

        for h in self._handles[1:-1]:
            cr.line_to(*h.pos)

        draw_line_end(handles[-1], handles[-2], self.draw_tail)

        cr.stroke()

        for shape, rect in (
            (self.shape_head, self._shape_head_rect),
            (self.shape_middle, self._shape_middle_rect),
            (self.shape_tail, self._shape_tail_rect),
        ):
            if shape:
                shape.draw(context, rect)

    def save(self, save_func):
        def save_connection(name, handle):
            c = self._connections.get_connection(handle)
            if c:
                save_func(name, c.connected)

        super().save(save_func)
        save_func("matrix", tuple(self.matrix))
        points = [tuple(map(float, h.pos)) for h in self.handles()]
        save_func("points", points)

        save_connection("head-connection", self.head)
        save_connection("tail-connection", self.tail)

    def load(self, name, value):
        if name == "matrix":
            self.matrix.set(*ast.literal_eval(value))
        elif name == "points":
            points = ast.literal_eval(value)
            for _ in range(len(points) - 2):
                h = Handle((0, 0))
                self._handles.insert(1, h)
                self.watch_handle(h)
            for i, p in enumerate(points):
                self.handles()[i].pos = p
            self._update_ports()
        elif name in ("head_connection", "head-connection"):
            self._load_head_connection = value
        elif name in ("tail_connection", "tail-connection"):
            self._load_tail_connection = value
        else:
            super().load(name, value)

    def postload(self):
        if self.orthogonal:
            self._set_orthogonal(self.orthogonal)

        if hasattr(self, "_load_head_connection"):
            postload_connect(self, self.head, self._load_head_connection)
            assert self._connections.get_connection(self.head)
            del self._load_head_connection

        if hasattr(self, "_load_tail_connection"):
            postload_connect(self, self.tail, self._load_tail_connection)
            assert self._connections.get_connection(self.tail)
            del self._load_tail_connection

        super().postload()

    def _on_orthogonal(self, event):
        self._set_orthogonal(event.new_value)

    def _on_horizontal(self, event):
        self._set_horizontal(event.new_value)

class MinimalValueConstraint(BaseConstraint):
    def __init__(self, var, min):
        super().__init__(var)
        self._min = min

    def solve_for(self, var):
        min = self._min
        var.value = max(var.value, min)
<commit_msg>Use stroke() function to draw line presentation<commit_after>from __future__ import annotations

import ast
from dataclasses import replace
from math import atan2

import gaphas
from gaphas.aspect.connector import ConnectionSink
from gaphas.aspect.connector import Connector as ConnectorAspect
from gaphas.connector import Handle
from gaphas.geometry import Rectangle, distance_rectangle_point
from gaphas.solver.constraint import BaseConstraint

from gaphor.core.modeling.diagram import Diagram
from gaphor.core.modeling.event import RevertibeEvent
from gaphor.core.modeling.presentation import Presentation, S
from gaphor.core.modeling.properties import attribute
from gaphor.core.styling import Style, merge_styles
from gaphor.diagram.shapes import stroke
from gaphor.diagram.text import TextAlign, text_point_at_line

class Named:
    """Marker for any NamedElement presentations."""

class Classified(Named):
    """Marker for Classifier presentations."""

def from_package_str(item):
    """Display name space info when it is different, then diagram's or parent's
    namespace."""
    subject = item.subject
    diagram = item.diagram

    if not (subject and diagram):
        return False

    namespace = subject.namespace
    parent = item.parent

    # if there is a parent (i.e. interaction)
    if parent and parent.subject and parent.subject.namespace is not namespace:
        return False

    return f"(from {namespace.name})" if namespace is not item.diagram.namespace else ""

def postload_connect(item: gaphas.Item, handle: gaphas.Handle, target: gaphas.Item):
    """Helper function: when loading a model, handles should be connected as
    part of the `postload` step.

    This function finds a suitable spot on the `target` item to connect
    the handle to.
    """
    connector = ConnectorAspect(item, handle, item.diagram.connections)
    sink = ConnectionSink(target, distance=1e4)
    connector.connect(sink)

class HandlePositionEvent(RevertibeEvent):

    requires_transaction = False

    def __init__(self, element, index, old_value):
        super().__init__(element)
        self.index = index
        self.old_value = old_value

    def revert(self, target):
        target.handles()[self.index].pos = self.old_value
        target.request_update()

class HandlePositionUpdate:
    def watch_handle(self, handle):
        handle.pos.add_handler(self._on_handle_position_update)

    def remove_watch_handle(self, handle):
        handle.pos.remove_handler(self._on_handle_position_update)

    def _on_handle_position_update(self, position, old):
        for index, handle in enumerate(self.handles()):  # type: ignore[attr-defined]
            if handle.pos is position:
                break
        else:
            return
        self.handle(HandlePositionEvent(self, index, old))  # type: ignore[attr-defined]

# Note: the official documentation is using the terms "Shape" and "Edge" for element and line.

class ElementPresentation(gaphas.Element, HandlePositionUpdate, Presentation[S]):
    """Presentation for Gaphas Element (box-like) items.

    To create a shape (boxes, text), assign a shape to `self.shape`. If
    the shape can change, for example, because styling needs to change,
    implement the method `update_shapes()` and set self.shape there.
    """

    _port_sides = ("top", "right", "bottom", "left")

    def __init__(self, diagram: Diagram, id=None, shape=None, width=100, height=50):
        super().__init__(connections=diagram.connections, diagram=diagram, id=id, width=width, height=height)  # type: ignore[call-arg]
        self._shape = shape
        for handle in self.handles():
            self.watch_handle(handle)

        diagram.connections.add_constraint(
            self, MinimalValueConstraint(self.min_width, width)
        )
        diagram.connections.add_constraint(
            self, MinimalValueConstraint(self.min_height, height)
        )

    def port_side(self, port):
        return self._port_sides[self._ports.index(port)]

    def _set_shape(self, shape):
        self._shape = shape
        self.request_update()

    shape = property(lambda s: s._shape, _set_shape)

    def update_shapes(self, event=None):
        """Updating the shape configuration, e.g. when extra elements have to
        be drawn or when styling changes."""

    def pre_update(self, context):
        if not self.shape:
            self.update_shapes()
        if self.shape:
            self.min_width, self.min_height = self.shape.size(context)

    def post_update(self, context):
        pass

    def draw(self, context):
        x, y = self.handles()[0].pos
        cairo = context.cairo
        cairo.translate(x, y)
        self._shape.draw(
            context,
            Rectangle(0, 0, self.width, self.height),
        )

    def save(self, save_func):
        save_func("matrix", tuple(self.matrix))
        save_func("width", self.width)
        save_func("height", self.height)
        super().save(save_func)

    def load(self, name, value):
        if name == "matrix":
            self.matrix.set(*ast.literal_eval(value))
        elif name == "width":
            self.width = ast.literal_eval(value)
        elif name == "height":
            self.height = ast.literal_eval(value)
        else:
            super().load(name, value)

    def postload(self):
        super().postload()
        self.update_shapes()

class MinimalValueConstraint(BaseConstraint):
    def __init__(self, var, min):
        super().__init__(var)
        self._min = min

    def solve_for(self, var):
        min = self._min
        if var is min:
            return
        var.value = max(var.value, min)

class LinePresentation(gaphas.Line, HandlePositionUpdate, Presentation[S]):
    def __init__(
        self,
        diagram: Diagram,
        id=None,
        style: Style = {},
        shape_head=None,
        shape_middle=None,
        shape_tail=None,
    ):
        super().__init__(connections=diagram.connections, diagram=diagram, id=id)  # type: ignore[call-arg]

        self.style = style
        self.shape_head = shape_head
        self.shape_middle = shape_middle
        self.shape_tail = shape_tail

        self.fuzziness = 2
        self._shape_head_rect = None
        self._shape_middle_rect = None
        self._shape_tail_rect = None
        self.watch("orthogonal", self._on_orthogonal).watch(
            "horizontal", self._on_horizontal
        )
        self.watch_handle(self.head)
        self.watch_handle(self.tail)

    head = property(lambda self: self._handles[0])
    tail = property(lambda self: self._handles[-1])

    orthogonal: attribute[int] = attribute("orthogonal", int, 0)
    horizontal: attribute[int] = attribute("horizontal", int, 0)

    @property
    def middle_shape_size(self) -> Rectangle:
        return self._shape_middle_rect

    def insert_handle(self, index: int, handle: Handle) -> None:
        super().insert_handle(index, handle)
        self.watch_handle(handle)

    def remove_handle(self, handle: Handle) -> None:
        self.remove_watch_handle(handle)
        super().remove_handle(handle)

    def pre_update(self, context):
        pass

    def post_update(self, context):
        def shape_bounds(shape, align):
            if shape:
                size = shape.size(context)
                x, y = text_point_at_line(points, size, align)
                return Rectangle(x, y, *size)

        points = [h.pos for h in self.handles()]
        self._shape_head_rect = shape_bounds(self.shape_head, TextAlign.LEFT)
        self._shape_middle_rect = shape_bounds(self.shape_middle, TextAlign.CENTER)
        self._shape_tail_rect = shape_bounds(self.shape_tail, TextAlign.RIGHT)

    def point(self, x, y):
        """Given a point (x, y) return the distance to the diagram item."""
        d0 = super().point(x, y)
        ds = [
            distance_rectangle_point(shape, (x, y))
            for shape in (
                self._shape_head_rect,
                self._shape_middle_rect,
                self._shape_tail_rect,
            )
            if shape
        ]
        return min(d0, *ds) if ds else d0

    def draw(self, context):
        def draw_line_end(end_handle, second_handle, draw):
            pos, p1 = end_handle.pos, second_handle.pos
            angle = atan2(p1.y - pos.y, p1.x - pos.x)
            cr.save()
            try:
                cr.translate(*pos)
                cr.rotate(angle)
                draw(context)
            finally:
                cr.restore()

        style = merge_styles(context.style, self.style)
        context = replace(context, style=style)

        cr = context.cairo

        handles = self._handles
        draw_line_end(handles[0], handles[1], self.draw_head)

        for h in self._handles[1:-1]:
            cr.line_to(*h.pos)

        draw_line_end(handles[-1], handles[-2], self.draw_tail)

        cr.set_dash(style.get("dash-style", ()), 0)
        stroke(context)

        for shape, rect in (
            (self.shape_head, self._shape_head_rect),
            (self.shape_middle, self._shape_middle_rect),
            (self.shape_tail, self._shape_tail_rect),
        ):
            if shape:
                shape.draw(context, rect)

    def save(self, save_func):
        def save_connection(name, handle):
            c = self._connections.get_connection(handle)
            if c:
                save_func(name, c.connected)

        super().save(save_func)
        save_func("matrix", tuple(self.matrix))
        points = [tuple(map(float, h.pos)) for h in self.handles()]
        save_func("points", points)

        save_connection("head-connection", self.head)
        save_connection("tail-connection", self.tail)

    def load(self, name, value):
        if name == "matrix":
            self.matrix.set(*ast.literal_eval(value))
        elif name == "points":
            points = ast.literal_eval(value)
            for _ in range(len(points) - 2):
                h = Handle((0, 0))
                self._handles.insert(1, h)
                self.watch_handle(h)
            for i, p in enumerate(points):
                self.handles()[i].pos = p
            self._update_ports()
        elif name in ("head_connection", "head-connection"):
            self._load_head_connection = value
        elif name in ("tail_connection", "tail-connection"):
            self._load_tail_connection = value
        else:
            super().load(name, value)

    def postload(self):
        if self.orthogonal:
            self._set_orthogonal(self.orthogonal)

        if hasattr(self, "_load_head_connection"):
            postload_connect(self, self.head, self._load_head_connection)
            assert self._connections.get_connection(self.head)
            del self._load_head_connection

        if hasattr(self, "_load_tail_connection"):
            postload_connect(self, self.tail, self._load_tail_connection)
            assert self._connections.get_connection(self.tail)
            del self._load_tail_connection

        super().postload()

    def _on_orthogonal(self, event):
        self._set_orthogonal(event.new_value)

    def _on_horizontal(self, event):
        self._set_horizontal(event.new_value)
<|endoftext|>


<commit_before>"""Copy / Paste functionality."""

from typing import Set

from gi.repository import Gdk, Gtk

from gaphor.abc import ActionProvider, Service
from gaphor.core import Transaction, action
from gaphor.core.modeling import Presentation
from gaphor.diagram.copypaste import copy, paste
from gaphor.ui.event import DiagramSelectionChanged

copy_buffer: object = None

class CopyService(Service, ActionProvider):
    """Copy/Cut/Paste functionality required a lot of thinking:

    Store a list of DiagramItems that have to be copied in a global
    'copy-buffer'.

    - In order to make copy/paste work, the load/save functions should be
      generalized to allow a subset to be saved/loaded (which is needed
      anyway for exporting/importing stereotype Profiles).
    - How much data should be saved? An example use case is to copy a diagram
      item, remove it (the underlying UML element is removed), and then paste
      the copied item. The diagram should act as if we have placed a copy of
      the removed item on the diagram and make the UML element visible again.
    """

    def __init__(self, event_manager, element_factory, diagrams):
        self.event_manager = event_manager
        self.element_factory = element_factory
        self.diagrams = diagrams

        self.clipboard = Gtk.Clipboard.get_default(Gdk.Display.get_default())
        self.clipboard.connect("owner_change", self.on_clipboard_owner_change)

    def shutdown(self):
        pass

    def on_clipboard_owner_change(self, clipboard, event):
        view = self.diagrams.get_current_view()
        if view and not view.is_focus():
            global copy_buffer
            copy_buffer = set()

    def copy(self, items):
        global copy_buffer
        if items:
            copy_buffer = copy(items)

    def paste(self, diagram):
        """Paste items in the copy-buffer to the diagram."""
        with Transaction(self.event_manager):
            # Create new id's that have to be used to create the items:
            new_items: Set[Presentation] = paste(
                copy_buffer, diagram, self.element_factory.lookup
            )

            # move pasted items a bit, so user can see result of his action :)
            for item in new_items:
                if item.parent not in new_items:
                    item.matrix.translate(10, 10)

        return new_items

    @action(
        name="edit-copy",
        shortcut="<Primary>c",
    )
    def copy_action(self):
        view = self.diagrams.get_current_view()
        if view.is_focus():
            self.clipboard.set_text("", -1)
            items = view.selection.selected_items
            self.copy(items)

    @action(name="edit-cut", shortcut="<Primary>x")
    def cut_action(self):
        view = self.diagrams.get_current_view()
        if view.is_focus():
            self.clipboard.set_text("", -1)
            items = view.selection.selected_items
            self.copy(items)
            with Transaction(self.event_manager):
                for i in list(items):
                    i.unlink()

    @action(name="edit-paste", shortcut="<Primary>v")
    def paste_action(self):
        view = self.diagrams.get_current_view()
        diagram = self.diagrams.get_current_diagram()
        if not (view and view.is_focus()):
            return

        if not copy_buffer:
            return

        new_items = self.paste(diagram)

        selection = view.selection
        selection.unselect_all()
        selection.select_items(*new_items)

        self.event_manager.handle(DiagramSelectionChanged(view, None, new_items))
<commit_msg>Properly disconnect signal handler in copy service<commit_after>"""Copy / Paste functionality."""

from typing import Set

from gi.repository import Gdk, Gtk

from gaphor.abc import ActionProvider, Service
from gaphor.core import Transaction, action
from gaphor.core.modeling import Presentation
from gaphor.diagram.copypaste import copy, paste
from gaphor.ui.event import DiagramSelectionChanged

copy_buffer: object = None

class CopyService(Service, ActionProvider):
    """Copy/Cut/Paste functionality required a lot of thinking:

    Store a list of DiagramItems that have to be copied in a global
    'copy-buffer'.

    - In order to make copy/paste work, the load/save functions should be
      generalized to allow a subset to be saved/loaded (which is needed
      anyway for exporting/importing stereotype Profiles).
    - How much data should be saved? An example use case is to copy a diagram
      item, remove it (the underlying UML element is removed), and then paste
      the copied item. The diagram should act as if we have placed a copy of
      the removed item on the diagram and make the UML element visible again.
    """

    def __init__(self, event_manager, element_factory, diagrams):
        self.event_manager = event_manager
        self.element_factory = element_factory
        self.diagrams = diagrams

        self.clipboard = Gtk.Clipboard.get_default(Gdk.Display.get_default())
        self._owner_change_id = self.clipboard.connect(
            "owner_change", self.on_clipboard_owner_change
        )

    def shutdown(self):
        self.clipboard.disconnect(self._owner_change_id)

    def on_clipboard_owner_change(self, clipboard, event):
        view = self.diagrams.get_current_view()
        if view and not view.is_focus():
            global copy_buffer
            copy_buffer = set()

    def copy(self, items):
        global copy_buffer
        if items:
            copy_buffer = copy(items)

    def paste(self, diagram):
        """Paste items in the copy-buffer to the diagram."""
        with Transaction(self.event_manager):
            # Create new id's that have to be used to create the items:
            new_items: Set[Presentation] = paste(
                copy_buffer, diagram, self.element_factory.lookup
            )

            # move pasted items a bit, so user can see result of his action :)
            for item in new_items:
                if item.parent not in new_items:
                    item.matrix.translate(10, 10)

        return new_items

    @action(
        name="edit-copy",
        shortcut="<Primary>c",
    )
    def copy_action(self):
        view = self.diagrams.get_current_view()
        if view.is_focus():
            self.clipboard.set_text("", -1)
            items = view.selection.selected_items
            self.copy(items)

    @action(name="edit-cut", shortcut="<Primary>x")
    def cut_action(self):
        view = self.diagrams.get_current_view()
        if view.is_focus():
            self.clipboard.set_text("", -1)
            items = view.selection.selected_items
            self.copy(items)
            with Transaction(self.event_manager):
                for i in list(items):
                    i.unlink()

    @action(name="edit-paste", shortcut="<Primary>v")
    def paste_action(self):
        view = self.diagrams.get_current_view()
        diagram = self.diagrams.get_current_diagram()
        if not (view and view.is_focus()):
            return

        if not copy_buffer:
            return

        new_items = self.paste(diagram)

        selection = view.selection
        selection.unselect_all()
        selection.select_items(*new_items)

        self.event_manager.handle(DiagramSelectionChanged(view, None, new_items))
<|endoftext|>


<commit_before># Copyright (C) 2011 REES Marche <http://www.reesmarche.org>
#
# This file is part of ``gasistafelice``.

# ``gasistafelice`` is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, version 3 of the License.
#
# ``gasistafelice`` is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with ``gasistafelice``. If not, see <http://www.gnu.org/licenses/>.

from flexi_auth.exceptions import WrongPermissionCheck
from flexi_auth.models import PrincipalParamRoleRelation
from django.contrib.auth.backends import ModelBackend

from django.contrib.auth.models import User

class AuthenticationParamRoleBackend(ModelBackend):
    """
    Django authentication backend to state that a User with no role cannot log in
    """

    def authenticate(self, username, password):
        """
        This is an authenticate-only backend, so it's good to be here! ;)
        """

        rv = None
        try:
            user = User.objects.get(username__iexact=username)
            if user.check_password(password):
                rv = user
        except User.DoesNotExist:
            rv = None

        if user:
            if not PrincipalParamRoleRelation.objects.filter(user=user).count():
                rv = None

        return rv

<commit_msg>Fixed "unhandled exception" on non-exitent user<commit_after># Copyright (C) 2011 REES Marche <http://www.reesmarche.org>
#
# This file is part of ``gasistafelice``.

# ``gasistafelice`` is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, version 3 of the License.
#
# ``gasistafelice`` is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with ``gasistafelice``. If not, see <http://www.gnu.org/licenses/>.

from flexi_auth.exceptions import WrongPermissionCheck
from flexi_auth.models import PrincipalParamRoleRelation
from django.contrib.auth.backends import ModelBackend

from django.contrib.auth.models import User

class AuthenticationParamRoleBackend(ModelBackend):
    """
    Django authentication backend to state that a User with no role cannot log in
    """

    def authenticate(self, username, password):
        """
        This is an authenticate-only backend, so it's good to be here! ;)
        """

        rv = None
        try:
            user = User.objects.get(username__iexact=username)
            if user.check_password(password):
                rv = user
        except User.DoesNotExist:
            rv = None

        if rv:
            if not PrincipalParamRoleRelation.objects.filter(user=user).count():
                rv = None

        return rv

<|endoftext|>


<commit_before>
def get_qs_filter_dict_from_str(flt_string):
    """Given a string which represent a querySet filter, return a filter.

    Useful to be passed as **dict in filter() methods.
    """

    flt = {}

    # build filter
    if "," not in flt_string:
        try:
            k,v = flt_string.split('=')
        except ValueError:
            raise
        flt[k] = v

    else:
        for couple in flt_string.split(','):
            try:
                k,v = couple.split('=')
            except ValueError:
                raise

            flt[k] = v
    return flt

def get_instance_dict_from_attrs(obj, attr_names):
    """Given a model instance and a list of attributes, returns a dict"""

    d = {}

    for attr_name in attr_names:
        # retrieve attributes and build dict for template

        # Support for nested attributes
        nested_attrs = attr_name.split('.')
        attr = obj
        for nested_attr in nested_attrs:
            attr = getattr(attr, nested_attr)

        if callable(attr):
            v = attr()
        else:
            v = attr
        d[attr_name] = v

    return d

<commit_msg>Added facility function queryset_from_iterable<commit_after>
def get_qs_filter_dict_from_str(flt_string):
    """Given a string which represent a querySet filter, return a filter.

    Useful to be passed as **dict in filter() methods.
    """

    flt = {}

    # build filter
    if "," not in flt_string:
        try:
            k,v = flt_string.split('=')
        except ValueError:
            raise
        flt[k] = v

    else:
        for couple in flt_string.split(','):
            try:
                k,v = couple.split('=')
            except ValueError:
                raise

            flt[k] = v
    return flt

def get_instance_dict_from_attrs(obj, attr_names):
    """Given a model instance and a list of attributes, returns a dict"""

    d = {}

    for attr_name in attr_names:
        # retrieve attributes and build dict for template

        # Support for nested attributes
        nested_attrs = attr_name.split('.')
        attr = obj
        for nested_attr in nested_attrs:
            attr = getattr(attr, nested_attr)

        if callable(attr):
            v = attr()
        else:
            v = attr
        d[attr_name] = v

    return d

def queryset_from_iterable(model, iterable):
    """
    Take a model class and an iterable containing instances of that model;
    return a ``QuerySet`` containing exactly those instances (barring duplicates, if any).

    If ``iterable`` contains an object that isn't an instance of ``model``, raise ``TypeError``.
    """
    # collect the set of IDs (i.e. primary keys) of model instances
    # contained in the given iterable (using a ``set`` object as accumulator,
    # in order to avoid duplicates)
    id_set = set()
    for obj in iterable:
        if obj.__class__ == model:
            id_set.add(obj.pk)
        else:
            raise TypeError(_(u"Can't create a %(model)s QuerySet: %(obj)s is not an instance of model %(model)s"))
    qs = model._default_manager.filter(pk__in=id_set)
    return qs
<|endoftext|>