<commit_before># Copyright (c) 2019-2020, Manfred Moitzi
# License: MIT License
from typing import Optional, Iterable, Tuple, TYPE_CHECKING, Dict, Set
from contextlib import contextmanager
from ezdxf.tools.handle import HandleGenerator
from ezdxf.lldxf.types import is_valid_handle
from ezdxf.entities.dxfentity import DXFEntity
from ezdxf.audit import AuditError, Auditor
from ezdxf.lldxf.const import DXFInternalEzdxfError
from ezdxf.entities.subentity import LinkedEntities
from ezdxf.entities import factory

if TYPE_CHECKING:
    from ezdxf.eztypes import TagWriter

DATABASE_EXCLUDE = {
    'SECTION', 'ENDSEC', 'EOF', 'TABLE', 'ENDTAB', 'CLASS', 'ACDSRECORD',
    'ACDSSCHEMA'
}

class EntityDB:
    """ A simple key/entity database.

    Every entity/object, except tables and sections, are represented as
    DXFEntity or inherited types, this entities are stored in the
    DXF document database, database-key is the `handle` as string.

    """

    class Trashcan:
        """ Store handles to entities which should be deleted later. """

        def __init__(self, db: 'EntityDB'):
            self._database = db._database
            self._handles: Set[str] = set()

        def add(self, handle: str):
            """ Put handle into trashcan to delete the entity later, this is
            required for deleting entities while iterating the database.
            """
            self._handles.add(handle)

        def clear(self):
            """ Remove handles in trashcan from database and destroy entities if
            still alive.
            """
            db = self._database
            for handle in self._handles:
                entity = db.get(handle)
                if entity and entity.is_alive:
                    entity.destroy()

                if handle in db:
                    del db[handle]

            self._handles.clear()

    def __init__(self):
        self._database: Dict[str, DXFEntity] = {}
        # DXF handles of entities to delete later:
        self.handles = HandleGenerator()
        self.locked: bool = False  # used only for debugging

    def __getitem__(self, handle: str) -> DXFEntity:
        """ Get entity by `handle`, does not filter destroyed entities nor
        entities in the trashcan.
        """
        return self._database[handle]

    def __setitem__(self, handle: str, entity: DXFEntity) -> None:
        """ Set `entity` for `handle`. """
        assert isinstance(handle, str), type(handle)
        assert isinstance(entity, DXFEntity), type(entity)
        assert entity.is_alive, 'Can not store destroyed entity.'
        if self.locked:
            raise DXFInternalEzdxfError('Locked entity database.')

        if handle == '0' or not is_valid_handle(handle):
            raise ValueError(f'Invalid handle {handle}.')
        self._database[handle] = entity

    def __delitem__(self, handle: str) -> None:
        """ Delete entity by `handle`. Removes entity only from database, does
        not destroy the entity.
        """
        if self.locked:
            raise DXFInternalEzdxfError('Locked entity database.')
        del self._database[handle]

    def __contains__(self, handle: str) -> bool:
        """ ``True`` if database contains `handle`. """
        if handle is None:
            return False
        assert isinstance(handle, str), type(handle)
        return handle in self._database

    def __len__(self) -> int:
        """ Count of database items. """
        return len(self._database)

    def __iter__(self) -> Iterable[str]:
        """ Iterable of all handles, does filter destroyed entities but not
        entities in the trashcan.
        """
        return self.keys()

    def get(self, handle: str) -> Optional[DXFEntity]:
        """ Returns entity for `handle` or ``None`` if no entry exist, does
        not filter destroyed entities.
        """
        return self._database.get(handle)

    def next_handle(self) -> str:
        """ Returns next unique handle."""
        while True:
            handle = self.handles.next()
            if handle not in self._database:
                return handle

    def keys(self) -> Iterable[str]:
        """ Iterable of all handles, does filter destroyed entities.
        """
        return (handle for handle, entity in self.items())

    def values(self) -> Iterable[DXFEntity]:
        """ Iterable of all entities, does filter destroyed entities.
        """
        return (entity for handle, entity in self.items())

    def items(self) -> Iterable[Tuple[str, DXFEntity]]:
        """ Iterable of all (handle, entities) pairs, does filter destroyed
        entities.
        """
        return (
            (handle, entity) for handle, entity in self._database.items()
            if entity.is_alive
        )

    def add(self, entity: DXFEntity) -> None:
        """ Add `entity` to database, assigns a new handle to the `entity`
        if :attr:`entity.dxf.handle` is ``None``. Adding the same entity
        multiple times is possible and creates only a single database entry.

        """
        if entity.dxftype() in DATABASE_EXCLUDE:
            if entity.dxf.handle is not None:
                # Mark existing entity handle as used to avoid
                # reassigning the same handle again.
                self[entity.dxf.handle] = entity
            return
        handle: str = entity.dxf.handle
        if handle is None:
            handle = self.next_handle()
            entity.update_handle(handle)
        self[handle] = entity

        # Add sub entities ATTRIB, VERTEX and SEQEND to database:
        # Add linked MTEXT columns to database:
        if hasattr(entity, 'add_sub_entities_to_entitydb'):
            entity.add_sub_entities_to_entitydb(self)

    def delete_entity(self, entity: DXFEntity) -> None:
        """ Remove `entity` from database and destroy the `entity`. """
        if entity.is_alive:
            del self[entity.dxf.handle]
            entity.destroy()

    def discard(self, entity: 'DXFEntity') -> None:
        """ Discard `entity` from database without destroying the `entity`. """
        if entity.is_alive:
            if hasattr(entity, 'process_sub_entities'):
                entity.process_sub_entities(lambda e: self.discard(e))

            handle = entity.dxf.handle
            try:
                del self._database[handle]
                entity.dxf.handle = None
            except KeyError:
                pass

    def duplicate_entity(self, entity: DXFEntity) -> DXFEntity:
        """ Duplicates `entity` and its sub entities (VERTEX, ATTRIB, SEQEND)
        and store them with new handles in the entity database.
        Graphical entities have to be added to a layout by
        :meth:`~ezdxf.layouts.BaseLayout.add_entity`.

        To import DXF entities from another drawing use the
        :class:`~ezdxf.addons.importer.Importer` add-on.

        A new owner handle will be set by adding the duplicated entity to a
        layout.

        """
        new_entity: DXFEntity = entity.copy()
        new_entity.dxf.handle = self.next_handle()
        factory.bind(new_entity, entity.doc)
        return new_entity

    def audit(self, auditor: 'Auditor'):
        """ Restore database integrity:

        - restore database entries with modified handles (key != entity.dxf.handle)
        - remove entities with invalid handles
        - empty trashcan - destroy all entities in the trashcan
        - removes destroyed database entries (purge)

        """
        assert self.locked is False, 'Database is locked!'
        add_entities = []

        with self.trashcan() as trash:
            for handle, entity in self.items():
                # Destroyed entities are already filtered!
                if not is_valid_handle(handle):
                    auditor.fixed_error(
                        code=AuditError.INVALID_ENTITY_HANDLE,
                        message=f'Removed entity {entity.dxftype()} with invalid '
                                f'handle "{handle}" from entity database.',
                    )
                    trash.add(handle)
                if handle != entity.dxf.get('handle'):
                    # database handle != stored entity handle
                    # prevent entity from being destroyed:
                    self._database[handle] = None
                    trash.add(handle)
                    add_entities.append(entity)

        # Remove all destroyed entities from database:
        self.purge()

        for entity in add_entities:
            handle = entity.dxf.get('handle')
            if handle is None:
                auditor.fixed_error(
                    code=AuditError.INVALID_ENTITY_HANDLE,
                    message=f'Removed entity {entity.dxftype()} without handle '
                            f'from entity database.',
                )
                continue
            if not is_valid_handle(handle) or handle == '0':
                auditor.fixed_error(
                    code=AuditError.INVALID_ENTITY_HANDLE,
                    message=f'Removed entity {entity.dxftype()} with invalid '
                            f'handle "{handle}" from entity database.',
                )
                continue
            self[handle] = entity

    def new_trashcan(self) -> 'EntityDB.Trashcan':
        """ Returns a new trashcan, empty trashcan manually by: :
        func:`Trashcan.clear()`.
        """
        return EntityDB.Trashcan(self)

    @contextmanager
    def trashcan(self) -> 'EntityDB.Trashcan':
        """ Returns a new trashcan in context manager mode, trashcan will be
        emptied when leaving context.
        """
        trashcan_ = self.new_trashcan()
        yield trashcan_
        # try ... finally is not required, in case of an exception the database
        # is maybe already in an unreliable state.
        trashcan_.clear()

    def purge(self) -> None:
        """ Remove all destroyed entities from database, but does not empty the
        trashcan.
        """
        # Important: operate on underlying data structure:
        db = self._database
        dead_handles = [
            handle for handle, entity in db.items()
            if not entity.is_alive
        ]
        for handle in dead_handles:
            del db[handle]

    def dxf_types_in_use(self) -> Set[str]:
        return set(entity.dxftype() for entity in self.values())

class EntitySpace:
    """
    An :class:`EntitySpace` is a collection of :class:`~ezdxf.entities.DXFEntity`
    objects, that stores only  references to :class:`DXFEntity` objects.

    The :class:`~ezdxf.layouts.Modelspace`, any :class:`~ezdxf.layouts.Paperspace`
    layout and :class:`~ezdxf.layouts.BlockLayout` objects have an
    :class:`EntitySpace` container to store their entities.

    """

    def __init__(self, entities=None):
        entities = entities or []
        self.entities = list(e for e in entities if e.is_alive)

    def __iter__(self) -> Iterable['DXFEntity']:
        """ Iterable of all entities, filters destroyed entities. """
        return (e for e in self.entities if e.is_alive)

    def __getitem__(self, index) -> 'DXFEntity':
        """ Get entity at index `item`

        :class:`EntitySpace` has a standard Python list like interface,
        therefore `index` can be any valid list indexing or slicing term, like
        a single index ``layout[-1]`` to get the last entity, or an index slice
        ``layout[:10]`` to get the first 10 or less entities as
        ``List[DXFEntity]``. Does not filter destroyed entities.

        """
        return self.entities[index]

    def __len__(self) -> int:
        """ Count of entities inluding destroyed entities. """
        return len(self.entities)

    def has_handle(self, handle: str) -> bool:
        """ ``True`` if `handle` is present, does filter destroyed entities. """
        assert isinstance(handle, str), type(handle)
        return any(e.dxf.handle == handle for e in self)

    def purge(self):
        """ Remove all destroyed entities from entity space. """
        self.entities = list(self)

    def add(self, entity: 'DXFEntity') -> None:
        """ Add `entity`. """
        assert isinstance(entity, DXFEntity), type(entity)
        assert entity.is_alive, 'Can not store destroyed entities'
        self.entities.append(entity)

    def extend(self, entities: Iterable['DXFEntity']) -> None:
        """ Add multiple `entities`."""
        for entity in entities:
            self.add(entity)

    def export_dxf(self, tagwriter: 'TagWriter') -> None:
        """ Export all entities into DXF file by `tagwriter`.

        (internal API)
        """
        for entity in iter(self):
            entity.export_dxf(tagwriter)

    def remove(self, entity: 'DXFEntity') -> None:
        """ Remove `entity`. """
        self.entities.remove(entity)

    def clear(self) -> None:
        """ Remove all entities. """
        # Do not destroy entities!
        self.entities = list()
<commit_msg>refactor type annotations<commit_after># Copyright (c) 2019-2021, Manfred Moitzi
# License: MIT License
from typing import Optional, Iterable, Tuple, TYPE_CHECKING, Dict, Set, List
from contextlib import contextmanager
from ezdxf.tools.handle import HandleGenerator
from ezdxf.lldxf.types import is_valid_handle
from ezdxf.entities.dxfentity import DXFEntity
from ezdxf.audit import AuditError, Auditor
from ezdxf.lldxf.const import DXFInternalEzdxfError
from ezdxf.entities import factory

if TYPE_CHECKING:
    from ezdxf.eztypes import TagWriter

DATABASE_EXCLUDE = {
    'SECTION', 'ENDSEC', 'EOF', 'TABLE', 'ENDTAB', 'CLASS', 'ACDSRECORD',
    'ACDSSCHEMA'
}

class EntityDB:
    """ A simple key/entity database.

    Every entity/object, except tables and sections, are represented as
    DXFEntity or inherited types, this entities are stored in the
    DXF document database, database-key is the `handle` as string.

    """

    class Trashcan:
        """ Store handles to entities which should be deleted later. """

        def __init__(self, db: 'EntityDB'):
            self._database = db._database
            self._handles: Set[str] = set()

        def add(self, handle: str):
            """ Put handle into trashcan to delete the entity later, this is
            required for deleting entities while iterating the database.
            """
            self._handles.add(handle)

        def clear(self):
            """ Remove handles in trashcan from database and destroy entities if
            still alive.
            """
            db = self._database
            for handle in self._handles:
                entity = db.get(handle)
                if entity and entity.is_alive:
                    entity.destroy()

                if handle in db:
                    del db[handle]

            self._handles.clear()

    def __init__(self):
        self._database: Dict[str, DXFEntity] = {}
        # DXF handles of entities to delete later:
        self.handles = HandleGenerator()
        self.locked: bool = False  # used only for debugging

    def __getitem__(self, handle: str) -> DXFEntity:
        """ Get entity by `handle`, does not filter destroyed entities nor
        entities in the trashcan.
        """
        return self._database[handle]

    def __setitem__(self, handle: str, entity: DXFEntity) -> None:
        """ Set `entity` for `handle`. """
        assert isinstance(handle, str), type(handle)
        assert isinstance(entity, DXFEntity), type(entity)
        assert entity.is_alive, 'Can not store destroyed entity.'
        if self.locked:
            raise DXFInternalEzdxfError('Locked entity database.')

        if handle == '0' or not is_valid_handle(handle):
            raise ValueError(f'Invalid handle {handle}.')
        self._database[handle] = entity

    def __delitem__(self, handle: str) -> None:
        """ Delete entity by `handle`. Removes entity only from database, does
        not destroy the entity.
        """
        if self.locked:
            raise DXFInternalEzdxfError('Locked entity database.')
        del self._database[handle]

    def __contains__(self, handle: str) -> bool:
        """ ``True`` if database contains `handle`. """
        if handle is None:
            return False
        assert isinstance(handle, str), type(handle)
        return handle in self._database

    def __len__(self) -> int:
        """ Count of database items. """
        return len(self._database)

    def __iter__(self) -> Iterable[str]:
        """ Iterable of all handles, does filter destroyed entities but not
        entities in the trashcan.
        """
        return self.keys()

    def get(self, handle: str) -> Optional[DXFEntity]:
        """ Returns entity for `handle` or ``None`` if no entry exist, does
        not filter destroyed entities.
        """
        return self._database.get(handle)

    def next_handle(self) -> str:
        """ Returns next unique handle."""
        while True:
            handle = self.handles.next()
            if handle not in self._database:
                return handle

    def keys(self) -> Iterable[str]:
        """ Iterable of all handles, does filter destroyed entities.
        """
        return (handle for handle, entity in self.items())

    def values(self) -> Iterable[DXFEntity]:
        """ Iterable of all entities, does filter destroyed entities.
        """
        return (entity for handle, entity in self.items())

    def items(self) -> Iterable[Tuple[str, DXFEntity]]:
        """ Iterable of all (handle, entities) pairs, does filter destroyed
        entities.
        """
        return (
            (handle, entity) for handle, entity in self._database.items()
            if entity.is_alive
        )

    def add(self, entity: DXFEntity) -> None:
        """ Add `entity` to database, assigns a new handle to the `entity`
        if :attr:`entity.dxf.handle` is ``None``. Adding the same entity
        multiple times is possible and creates only a single database entry.

        """
        if entity.dxftype() in DATABASE_EXCLUDE:
            if entity.dxf.handle is not None:
                # Mark existing entity handle as used to avoid
                # reassigning the same handle again.
                self[entity.dxf.handle] = entity
            return
        handle: str = entity.dxf.handle
        if handle is None:
            handle = self.next_handle()
            entity.update_handle(handle)
        self[handle] = entity

        # Add sub entities ATTRIB, VERTEX and SEQEND to database:
        # Add linked MTEXT columns to database:
        if hasattr(entity, 'add_sub_entities_to_entitydb'):
            entity.add_sub_entities_to_entitydb(self)

    def delete_entity(self, entity: DXFEntity) -> None:
        """ Remove `entity` from database and destroy the `entity`. """
        if entity.is_alive:
            del self[entity.dxf.handle]
            entity.destroy()

    def discard(self, entity: DXFEntity) -> None:
        """ Discard `entity` from database without destroying the `entity`. """
        if entity.is_alive:
            if hasattr(entity, 'process_sub_entities'):
                entity.process_sub_entities(lambda e: self.discard(e))

            handle = entity.dxf.handle
            try:
                del self._database[handle]
                entity.dxf.handle = None
            except KeyError:
                pass

    def duplicate_entity(self, entity: DXFEntity) -> DXFEntity:
        """ Duplicates `entity` and its sub entities (VERTEX, ATTRIB, SEQEND)
        and store them with new handles in the entity database.
        Graphical entities have to be added to a layout by
        :meth:`~ezdxf.layouts.BaseLayout.add_entity`.

        To import DXF entities from another drawing use the
        :class:`~ezdxf.addons.importer.Importer` add-on.

        A new owner handle will be set by adding the duplicated entity to a
        layout.

        """
        new_entity: DXFEntity = entity.copy()
        new_entity.dxf.handle = self.next_handle()
        factory.bind(new_entity, entity.doc)
        return new_entity

    def audit(self, auditor: 'Auditor'):
        """ Restore database integrity:

        - restore database entries with modified handles (key != entity.dxf.handle)
        - remove entities with invalid handles
        - empty trashcan - destroy all entities in the trashcan
        - removes destroyed database entries (purge)

        """
        assert self.locked is False, 'Database is locked!'
        add_entities = []

        with self.trashcan() as trash:
            for handle, entity in self.items():
                # Destroyed entities are already filtered!
                if not is_valid_handle(handle):
                    auditor.fixed_error(
                        code=AuditError.INVALID_ENTITY_HANDLE,
                        message=f'Removed entity {entity.dxftype()} with invalid '
                                f'handle "{handle}" from entity database.',
                    )
                    trash.add(handle)
                if handle != entity.dxf.get('handle'):
                    # database handle != stored entity handle
                    # prevent entity from being destroyed:
                    self._database[handle] = None
                    trash.add(handle)
                    add_entities.append(entity)

        # Remove all destroyed entities from database:
        self.purge()

        for entity in add_entities:
            handle = entity.dxf.get('handle')
            if handle is None:
                auditor.fixed_error(
                    code=AuditError.INVALID_ENTITY_HANDLE,
                    message=f'Removed entity {entity.dxftype()} without handle '
                            f'from entity database.',
                )
                continue
            if not is_valid_handle(handle) or handle == '0':
                auditor.fixed_error(
                    code=AuditError.INVALID_ENTITY_HANDLE,
                    message=f'Removed entity {entity.dxftype()} with invalid '
                            f'handle "{handle}" from entity database.',
                )
                continue
            self[handle] = entity

    def new_trashcan(self) -> 'EntityDB.Trashcan':
        """ Returns a new trashcan, empty trashcan manually by: :
        func:`Trashcan.clear()`.
        """
        return EntityDB.Trashcan(self)

    @contextmanager
    def trashcan(self) -> 'EntityDB.Trashcan':
        """ Returns a new trashcan in context manager mode, trashcan will be
        emptied when leaving context.
        """
        trashcan_ = self.new_trashcan()
        yield trashcan_
        # try ... finally is not required, in case of an exception the database
        # is maybe already in an unreliable state.
        trashcan_.clear()

    def purge(self) -> None:
        """ Remove all destroyed entities from database, but does not empty the
        trashcan.
        """
        # Important: operate on underlying data structure:
        db = self._database
        dead_handles = [
            handle for handle, entity in db.items()
            if not entity.is_alive
        ]
        for handle in dead_handles:
            del db[handle]

    def dxf_types_in_use(self) -> Set[str]:
        return set(entity.dxftype() for entity in self.values())

class EntitySpace:
    """
    An :class:`EntitySpace` is a collection of :class:`~ezdxf.entities.DXFEntity`
    objects, that stores only  references to :class:`DXFEntity` objects.

    The :class:`~ezdxf.layouts.Modelspace`, any :class:`~ezdxf.layouts.Paperspace`
    layout and :class:`~ezdxf.layouts.BlockLayout` objects have an
    :class:`EntitySpace` container to store their entities.

    """

    def __init__(self, entities: Iterable[DXFEntity] = None):
        self.entities: List[DXFEntity] = list(e for e in entities if e.is_alive) \
            if entities else []

    def __iter__(self) -> Iterable[DXFEntity]:
        """ Iterable of all entities, filters destroyed entities. """
        return (e for e in self.entities if e.is_alive)

    def __getitem__(self, index) -> DXFEntity:
        """ Get entity at index `item`

        :class:`EntitySpace` has a standard Python list like interface,
        therefore `index` can be any valid list indexing or slicing term, like
        a single index ``layout[-1]`` to get the last entity, or an index slice
        ``layout[:10]`` to get the first 10 or less entities as
        ``List[DXFEntity]``. Does not filter destroyed entities.

        """
        return self.entities[index]

    def __len__(self) -> int:
        """ Count of entities including destroyed entities. """
        return len(self.entities)

    def has_handle(self, handle: str) -> bool:
        """ ``True`` if `handle` is present, does filter destroyed entities. """
        assert isinstance(handle, str), type(handle)
        return any(e.dxf.handle == handle for e in self)

    def purge(self):
        """ Remove all destroyed entities from entity space. """
        self.entities = list(self)

    def add(self, entity: DXFEntity) -> None:
        """ Add `entity`. """
        assert isinstance(entity, DXFEntity), type(entity)
        assert entity.is_alive, 'Can not store destroyed entities'
        self.entities.append(entity)

    def extend(self, entities: Iterable[DXFEntity]) -> None:
        """ Add multiple `entities`."""
        for entity in entities:
            self.add(entity)

    def export_dxf(self, tagwriter: 'TagWriter') -> None:
        """ Export all entities into DXF file by `tagwriter`.

        (internal API)
        """
        for entity in iter(self):
            entity.export_dxf(tagwriter)

    def remove(self, entity: DXFEntity) -> None:
        """ Remove `entity`. """
        self.entities.remove(entity)

    def clear(self) -> None:
        """ Remove all entities. """
        # Do not destroy entities!
        self.entities = list()
<|endoftext|>


<commit_before># coding=utf-8

from django.contrib import admin

from feedback.models import Person, Veranstaltung, Semester, Einstellung, \
    Mailvorlage, Kommentar, Tutor, BarcodeScanner, BarcodeScannEvent, BarcodeAllowedState
from feedback.models.base import Log

def status_angelegt(modeladmin, request, queryset):
    queryset.update(status=100)
    for veranstaltung in queryset:
        veranstaltung.log(True, False)
status_angelegt.short_description = 'Status: angelegt'

def status_gedruckt(modeladmin, request, queryset):
    queryset.update(status=600)
    for veranstaltung in queryset:
        veranstaltung.log(True, False)
status_gedruckt.short_description = 'Status: gedruckt'

def status_versandt(modeladmin, request, queryset):
    queryset.update(status=700)
    for veranstaltung in queryset:
        veranstaltung.log(True, False)
status_versandt.short_description = 'Status: versandt'

def status_boegen_eingegangen(modeladmin, request, queryset):
    queryset.update(status=800)
    for veranstaltung in queryset:
        veranstaltung.log(True, False)
status_boegen_eingegangen.short_description = 'Status: eingegangen'

def status_boegen_gescannt(modeladmin, request, queryset):
    queryset.update(status=900)
    for veranstaltung in queryset:
        veranstaltung.log(True, False)
status_boegen_gescannt.short_description = 'Status: gescannt'

class PersonAdmin(admin.ModelAdmin):
    list_display = ('__unicode__', 'email')
    search_fields = ['vorname', 'nachname', 'email', ]

class VeranstaltungAdmin(admin.ModelAdmin):
    fieldsets = [
        ('Stammdaten', {'fields':
                            ['typ', 'name', 'semester', 'status', 'lv_nr', 'grundstudium', 'evaluieren',
                             'veranstalter', 'link_veranstalter',
                             ]}),
        ('Bestellung', {'fields': ['sprache', 'anzahl', 'verantwortlich', 'ergebnis_empfaenger', 'auswertungstermin',
                                   'freiefrage1', 'freiefrage2', 'kleingruppen', ]}),
    ]
    list_display = ('typ', 'name', 'semester', 'grundstudium', 'evaluieren', 'anzahl',
                    'sprache', 'status', 'veranstalter_list')
    list_display_links = ['name']
    list_filter = ('typ', 'semester', 'grundstudium', 'evaluieren', 'sprache')
    search_fields = ['name']
    filter_horizontal = ('veranstalter', 'ergebnis_empfaenger')  # @see http://stackoverflow.com/a/5386871
    readonly_fields = ('link_veranstalter',)

class VeranstaltungStatus(Veranstaltung):
    class Meta:
        verbose_name = 'Status der Veranstaltung'
        verbose_name_plural = 'Status der Veranstaltungen'
        proxy = True

class VeranstaltungStatusAdmin(admin.ModelAdmin):
    list_display = ('lv_nr', 'name', 'status')
    list_display_links = ['name']
    list_filter = ('lv_nr', 'name', 'status')
    search_fields = ['name']
    actions = [
        status_angelegt, status_gedruckt, status_versandt, status_boegen_eingegangen, status_boegen_gescannt
    ]

class LogAdmin(admin.ModelAdmin):
    list_display = ('veranstaltung', 'timestamp', 'status', 'verursacher', 'interface')
    list_filter = ('veranstaltung',)
    ordering = ('timestamp',)

class SemesterAdmin(admin.ModelAdmin):
    list_display = ('__unicode__', 'sichtbarkeit', 'fragebogen')
    list_filter = ('sichtbarkeit', 'fragebogen')
    ordering = ('-semester',)

class EinstellungAdmin(admin.ModelAdmin):
    list_display = ('name', 'wert')
    list_editable = ('wert',)

class MailvorlageAdmin(admin.ModelAdmin):
    list_display = ('subject',)

class KommentarAdmin(admin.ModelAdmin):
    list_display = ('typ', 'name', 'semester', 'autor')
    list_display_links = ('name',)

class TutorAdmin(admin.ModelAdmin):
    fieldsets = [
        ('Stammdaten', {'fields':
                            ['vorname', 'nachname', 'email',
                             ]}),
        ('Lehrveranstaltung', {'fields':
                                   ['veranstaltung', 'nummer', 'anmerkung'
                                    ]}),
    ]
    list_display = ('vorname', 'nachname', 'nummer', 'veranstaltung')
    search_fields = ('vorname', 'nachname')
    ordering = ('veranstaltung', 'nummer')

    def render_change_form(self, request, context, *args, **kwargs):
        # Limit Auswahl zum aktuellen Semester
        context['adminform'].form.fields['veranstaltung'].queryset = Veranstaltung.objects.filter(
            semester=Semester.current())
        return super(TutorAdmin, self).render_change_form(request, context, args, kwargs)

class BarcodeScannEventAdmin(admin.ModelAdmin):
    list_display = ('veranstaltung', 'timestamp',)
    readonly_fields = ('veranstaltung', 'timestamp',)

class BarcodeAllowedStateInline(admin.TabularInline):
    model = BarcodeAllowedState

class BarcodeScannerAdmin(admin.ModelAdmin):
    inlines = [
        BarcodeAllowedStateInline,
    ]
    list_display = ('token', 'description')

admin.site.register(Person, PersonAdmin)
admin.site.register(Veranstaltung, VeranstaltungAdmin)
admin.site.register(Semester, SemesterAdmin)
admin.site.register(Einstellung, EinstellungAdmin)
admin.site.register(Mailvorlage, MailvorlageAdmin)
admin.site.register(Kommentar, KommentarAdmin)
admin.site.register(Tutor, TutorAdmin)
admin.site.register(BarcodeScannEvent, BarcodeScannEventAdmin)
admin.site.register(BarcodeScanner, BarcodeScannerAdmin)
admin.site.register(VeranstaltungStatus, VeranstaltungStatusAdmin)
admin.site.register(Log, LogAdmin)
<commit_msg>refactored setting of status in admin actions<commit_after># coding=utf-8

from django.contrib import admin

from feedback.models import Person, Veranstaltung, Semester, Einstellung, \
    Mailvorlage, Kommentar, Tutor, BarcodeScanner, BarcodeScannEvent, BarcodeAllowedState
from feedback.models.base import Log

def status_angelegt(modeladmin, request, queryset):
    queryset.update(status=Veranstaltung.STATUS_ANGELEGT)
    for veranstaltung in queryset:
        veranstaltung.log(True, False)
status_angelegt.short_description = 'Status: angelegt'

def status_gedruckt(modeladmin, request, queryset):
    queryset.update(status=Veranstaltung.STATUS_GEDRUCKT)
    for veranstaltung in queryset:
        veranstaltung.log(True, False)
status_gedruckt.short_description = 'Status: gedruckt'

def status_versandt(modeladmin, request, queryset):
    queryset.update(status=Veranstaltung.STATUS_VERSANDT)
    for veranstaltung in queryset:
        veranstaltung.log(True, False)
status_versandt.short_description = 'Status: versandt'

def status_boegen_eingegangen(modeladmin, request, queryset):
    queryset.update(status=Veranstaltung.STATUS_BOEGEN_EINGEGANGEN)
    for veranstaltung in queryset:
        veranstaltung.log(True, False)
status_boegen_eingegangen.short_description = 'Status: eingegangen'

def status_boegen_gescannt(modeladmin, request, queryset):
    queryset.update(status=Veranstaltung.STATUS_BOEGEN_GESCANNT)
    for veranstaltung in queryset:
        veranstaltung.log(True, False)
status_boegen_gescannt.short_description = 'Status: gescannt'

class PersonAdmin(admin.ModelAdmin):
    list_display = ('__unicode__', 'email')
    search_fields = ['vorname', 'nachname', 'email', ]

class VeranstaltungAdmin(admin.ModelAdmin):
    fieldsets = [
        ('Stammdaten', {'fields':
                            ['typ', 'name', 'semester', 'status', 'lv_nr', 'grundstudium', 'evaluieren',
                             'veranstalter', 'link_veranstalter',
                             ]}),
        ('Bestellung', {'fields': ['sprache', 'anzahl', 'verantwortlich', 'ergebnis_empfaenger', 'auswertungstermin',
                                   'freiefrage1', 'freiefrage2', 'kleingruppen', ]}),
    ]
    list_display = ('typ', 'name', 'semester', 'grundstudium', 'evaluieren', 'anzahl',
                    'sprache', 'status', 'veranstalter_list')
    list_display_links = ['name']
    list_filter = ('typ', 'semester', 'grundstudium', 'evaluieren', 'sprache')
    search_fields = ['name']
    filter_horizontal = ('veranstalter', 'ergebnis_empfaenger')  # @see http://stackoverflow.com/a/5386871
    readonly_fields = ('link_veranstalter',)

class VeranstaltungStatus(Veranstaltung):
    class Meta:
        verbose_name = 'Status der Veranstaltung'
        verbose_name_plural = 'Status der Veranstaltungen'
        proxy = True

class VeranstaltungStatusAdmin(admin.ModelAdmin):
    list_display = ('lv_nr', 'name', 'status')
    list_display_links = ['name']
    list_filter = ('lv_nr', 'name', 'status')
    search_fields = ['name']
    actions = [
        status_angelegt, status_gedruckt, status_versandt, status_boegen_eingegangen, status_boegen_gescannt
    ]

class LogAdmin(admin.ModelAdmin):
    list_display = ('veranstaltung', 'timestamp', 'status', 'verursacher', 'interface')
    list_filter = ('veranstaltung',)
    ordering = ('timestamp',)

class SemesterAdmin(admin.ModelAdmin):
    list_display = ('__unicode__', 'sichtbarkeit', 'fragebogen')
    list_filter = ('sichtbarkeit', 'fragebogen')
    ordering = ('-semester',)

class EinstellungAdmin(admin.ModelAdmin):
    list_display = ('name', 'wert')
    list_editable = ('wert',)

class MailvorlageAdmin(admin.ModelAdmin):
    list_display = ('subject',)

class KommentarAdmin(admin.ModelAdmin):
    list_display = ('typ', 'name', 'semester', 'autor')
    list_display_links = ('name',)

class TutorAdmin(admin.ModelAdmin):
    fieldsets = [
        ('Stammdaten', {'fields':
                            ['vorname', 'nachname', 'email',
                             ]}),
        ('Lehrveranstaltung', {'fields':
                                   ['veranstaltung', 'nummer', 'anmerkung'
                                    ]}),
    ]
    list_display = ('vorname', 'nachname', 'nummer', 'veranstaltung')
    search_fields = ('vorname', 'nachname')
    ordering = ('veranstaltung', 'nummer')

    def render_change_form(self, request, context, *args, **kwargs):
        # Limit Auswahl zum aktuellen Semester
        context['adminform'].form.fields['veranstaltung'].queryset = Veranstaltung.objects.filter(
            semester=Semester.current())
        return super(TutorAdmin, self).render_change_form(request, context, args, kwargs)

class BarcodeScannEventAdmin(admin.ModelAdmin):
    list_display = ('veranstaltung', 'timestamp',)
    readonly_fields = ('veranstaltung', 'timestamp',)

class BarcodeAllowedStateInline(admin.TabularInline):
    model = BarcodeAllowedState

class BarcodeScannerAdmin(admin.ModelAdmin):
    inlines = [
        BarcodeAllowedStateInline,
    ]
    list_display = ('token', 'description')

admin.site.register(Person, PersonAdmin)
admin.site.register(Veranstaltung, VeranstaltungAdmin)
admin.site.register(Semester, SemesterAdmin)
admin.site.register(Einstellung, EinstellungAdmin)
admin.site.register(Mailvorlage, MailvorlageAdmin)
admin.site.register(Kommentar, KommentarAdmin)
admin.site.register(Tutor, TutorAdmin)
admin.site.register(BarcodeScannEvent, BarcodeScannEventAdmin)
admin.site.register(BarcodeScanner, BarcodeScannerAdmin)
admin.site.register(VeranstaltungStatus, VeranstaltungStatusAdmin)
admin.site.register(Log, LogAdmin)
<|endoftext|>


<commit_before>"""
gallerize.main
~~~~~~~~~~~~~~

:Copyright: 2007-2021 Jochen Kupperschmidt
:License: MIT, see LICENSE for details.
"""

from __future__ import annotations
from collections import defaultdict
from dataclasses import dataclass
from itertools import islice
import os
from pathlib import Path
import shutil
import subprocess
import sys
from typing import (
    Any,
    Dict,
    Iterable,
    Iterator,
    List,
    Optional,
    Sequence,
    Tuple,
)

from jinja2 import Environment, PackageLoader

IMAGE_CAPTION_EXTENSION: str = '.txt'
TEMPLATE_EXTENSION: str = '.html'
OUTPUT_HTML_EXTENSION: str = '.html'

PATH: str = os.path.dirname(os.path.abspath(__file__))
PATH_STATIC: str = os.path.join(PATH, 'static')
TEMPLATE_ENVIRONMENT: Environment = Environment(
    autoescape=True,
    loader=PackageLoader(__package__, 'templates'),
    trim_blocks=False,
)

@dataclass(frozen=True)
class Dimension:
    width: int
    height: int

def debug(message: str, *args: Any) -> None:
    print(message.format(*args))

def resize_image(
    src_filename: str, dst_filename: str, max_dimension: Dimension
) -> None:
    """Create a resized (and antialiased) version of an image."""
    dimension_str = f'{max_dimension.width:d}x{max_dimension.height:d}'
    cmd = ['convert', '-resize', dimension_str, src_filename, dst_filename]
    subprocess.check_call(cmd)

def render_html_to_file(
    template_name: str, context: Dict[str, Any], path: str, page_name: str
) -> None:
    """Render the template and write the result to the given file."""
    context['url_for_page'] = lambda page: page + OUTPUT_HTML_EXTENSION
    html = render_template(template_name + TEMPLATE_EXTENSION, **context)

    filename = os.path.join(path, page_name + OUTPUT_HTML_EXTENSION)
    write_file(filename, html)

def render_template(template_filename: str, **context: Dict[str, Any]) -> str:
    return TEMPLATE_ENVIRONMENT.get_template(template_filename).render(context)

def write_file(filename: str, content: str) -> None:
    debug('Writing "{}" ...', filename)
    Path(filename).write_text(content, encoding='utf-8')

def create_gallery(
    title: str,
    destination_path: str,
    resize: bool,
    max_image_size: Dimension,
    max_thumbnail_size: Dimension,
    full_image_filenames: List[str],
) -> Gallery:
    images = [create_image(image) for image in sorted(full_image_filenames)]
    link_images(images)

    return Gallery(
        title=title,
        destination_path=destination_path,
        resize=resize,
        max_image_size=max_image_size,
        max_thumbnail_size=max_thumbnail_size,
        images=images,
    )

@dataclass(frozen=True)
class Gallery:
    title: str
    destination_path: str
    resize: bool
    max_image_size: Dimension
    max_thumbnail_size: Dimension
    images: List[Image]

def link_images(images: List[Image]) -> None:
    """Assign the predecessor and successor for every image."""
    for previous_image, image, next_image in window(images):
        image.previous_image = previous_image
        image.next_image = next_image

def generate_gallery(gallery: Gallery) -> None:
    # Create destination path if it doesn't exist.
    if not os.path.exists(gallery.destination_path):
        debug(
            'Destination path "{}" does not exist, creating it.',
            gallery.destination_path,
        )
        os.mkdir(gallery.destination_path)

    generate_images(gallery)
    render_html_pages(gallery)
    copy_additional_static_files(gallery.destination_path)
    debug('Done.')

def generate_images(gallery: Gallery) -> None:
    for image in gallery.images:
        generate_image(image, gallery)
        generate_thumbnail(image, gallery)
        image.caption = load_caption(image)

def render_html_pages(gallery: Gallery) -> None:
    for image in gallery.images:
        render_html_page(gallery, image)
    render_html_index_page(gallery)

def render_html_index_page(gallery: Gallery) -> None:
    """Create an HTML document of thumbnails that link to single image
    documents.
    """
    context = {'gallery': gallery}
    render_html_to_file('index', context, gallery.destination_path, 'index')

def copy_additional_static_files(destination_path: str) -> None:
    if not os.path.exists(PATH_STATIC):
        debug(
            'Path "{}", does not exist; not copying any static files.',
            PATH_STATIC,
        )
        return

    filenames = list(sorted(os.listdir(PATH_STATIC)))
    if not filenames:
        debug('No static files to copy.')
    for filename in filenames:
        debug('Copying static file "{}" ...', filename)
        shutil.copy(
            os.path.join(PATH_STATIC, filename),
            os.path.join(destination_path, filename),
        )

def window(
    images: Iterable[Image],
) -> Iterator[Tuple[Optional[Image], Image, Optional[Image]]]:
    """Return a sliding window over the images.

    Returns a triple. Its first and third element can be `None`, but the
    second element never is.
    """
    n = 3
    padded_list: List[Optional[Image]] = list(images)
    padded_list.insert(0, None)
    padded_list.append(None)

    it = iter(padded_list)
    result = tuple(islice(it, n))

    if len(result) == n:
        yield result  # type: ignore

    for elem in it:
        result = result[1:] + (elem,)
        yield result  # type: ignore

@dataclass
class Image:
    full_filename: str
    path: str
    filename: str
    thumbnail_filename: str
    page_name: str
    previous_image: Optional[Image] = None
    next_image: Optional[Image] = None

def create_image(full_filename: str) -> Image:
    path, filename = os.path.split(full_filename)
    basename, extension = os.path.splitext(filename)
    thumbnail_filename = f'{basename}_t{extension}'

    return Image(
        full_filename=full_filename,
        path=path,
        filename=filename,
        thumbnail_filename=thumbnail_filename,
        page_name=basename,
    )

def generate_image(image: Image, gallery: Gallery) -> None:
    """Create a (optionally resized) copy of an image."""
    destination_filename = os.path.join(
        gallery.destination_path, image.filename
    )
    if gallery.resize:
        # Resize image.
        debug('Resizing image "{}" ...', image.full_filename)
        resize_image(
            image.full_filename,
            destination_filename,
            gallery.max_image_size,
        )
    else:
        # Copy image.
        debug('Copying image "{}" ...', image.full_filename)
        shutil.copy(image.full_filename, destination_filename)

def generate_thumbnail(image: Image, gallery: Gallery) -> None:
    """Create a preview of an image."""
    debug('Creating thumbnail "{}" ...', image.thumbnail_filename)
    destination_filename = os.path.join(
        gallery.destination_path, image.thumbnail_filename
    )
    resize_image(
        image.full_filename,
        destination_filename,
        gallery.max_thumbnail_size,
    )

def load_caption(image: Image) -> str:
    """Load image caption from file."""
    caption_filename = os.path.join(
        image.path, image.filename + IMAGE_CAPTION_EXTENSION
    )
    return _read_first_line(caption_filename)

def _read_first_line(filename: str) -> Optional[str]:
    """Read the first line from the specified file."""
    try:
        text = Path(filename).read_text(encoding='utf-8')
        first_line = text.splitlines()[0]
        return first_line.strip()
    except IOError:
        # File does not exist (OK) or cannot be read (not really OK).
        return None

def render_html_page(gallery: Gallery, image: Image) -> None:
    """Create an HTML document for a single image."""
    context = {'gallery': gallery, 'image': image}
    render_html_to_file(
        'view', context, gallery.destination_path, image.page_name
    )

# -------------------------------------------------------------------- #

def handle_duplicate_filenames(paths) -> None:
    duplicates = find_duplicate_filenames(paths)
    if not duplicates:
        return

    print('Found duplicate filenames:')
    for filename, paths in duplicates:
        print(f'  + "{filename}" appears in the following paths:')
        for path in paths:
            print(f'    - {path}')

    sys.exit('Clashing target filenames, aborting.')

def find_duplicate_filenames(
    paths: Iterable[str],
) -> List[Tuple[str, List[str]]]:
    d = defaultdict(list)
    for path in paths:
        key = os.path.basename(path).lower()
        d[key].append(path)

    return [item for item in d.items() if len(item[1]) > 1]
<commit_msg>Add missing type hint<commit_after>"""
gallerize.main
~~~~~~~~~~~~~~

:Copyright: 2007-2021 Jochen Kupperschmidt
:License: MIT, see LICENSE for details.
"""

from __future__ import annotations
from collections import defaultdict
from dataclasses import dataclass
from itertools import islice
import os
from pathlib import Path
import shutil
import subprocess
import sys
from typing import (
    Any,
    Dict,
    Iterable,
    Iterator,
    List,
    Optional,
    Sequence,
    Tuple,
)

from jinja2 import Environment, PackageLoader

IMAGE_CAPTION_EXTENSION: str = '.txt'
TEMPLATE_EXTENSION: str = '.html'
OUTPUT_HTML_EXTENSION: str = '.html'

PATH: str = os.path.dirname(os.path.abspath(__file__))
PATH_STATIC: str = os.path.join(PATH, 'static')
TEMPLATE_ENVIRONMENT: Environment = Environment(
    autoescape=True,
    loader=PackageLoader(__package__, 'templates'),
    trim_blocks=False,
)

@dataclass(frozen=True)
class Dimension:
    width: int
    height: int

def debug(message: str, *args: Any) -> None:
    print(message.format(*args))

def resize_image(
    src_filename: str, dst_filename: str, max_dimension: Dimension
) -> None:
    """Create a resized (and antialiased) version of an image."""
    dimension_str = f'{max_dimension.width:d}x{max_dimension.height:d}'
    cmd = ['convert', '-resize', dimension_str, src_filename, dst_filename]
    subprocess.check_call(cmd)

def render_html_to_file(
    template_name: str, context: Dict[str, Any], path: str, page_name: str
) -> None:
    """Render the template and write the result to the given file."""
    context['url_for_page'] = lambda page: page + OUTPUT_HTML_EXTENSION
    html = render_template(template_name + TEMPLATE_EXTENSION, **context)

    filename = os.path.join(path, page_name + OUTPUT_HTML_EXTENSION)
    write_file(filename, html)

def render_template(template_filename: str, **context: Dict[str, Any]) -> str:
    return TEMPLATE_ENVIRONMENT.get_template(template_filename).render(context)

def write_file(filename: str, content: str) -> None:
    debug('Writing "{}" ...', filename)
    Path(filename).write_text(content, encoding='utf-8')

def create_gallery(
    title: str,
    destination_path: str,
    resize: bool,
    max_image_size: Dimension,
    max_thumbnail_size: Dimension,
    full_image_filenames: List[str],
) -> Gallery:
    images = [create_image(image) for image in sorted(full_image_filenames)]
    link_images(images)

    return Gallery(
        title=title,
        destination_path=destination_path,
        resize=resize,
        max_image_size=max_image_size,
        max_thumbnail_size=max_thumbnail_size,
        images=images,
    )

@dataclass(frozen=True)
class Gallery:
    title: str
    destination_path: str
    resize: bool
    max_image_size: Dimension
    max_thumbnail_size: Dimension
    images: List[Image]

def link_images(images: List[Image]) -> None:
    """Assign the predecessor and successor for every image."""
    for previous_image, image, next_image in window(images):
        image.previous_image = previous_image
        image.next_image = next_image

def generate_gallery(gallery: Gallery) -> None:
    # Create destination path if it doesn't exist.
    if not os.path.exists(gallery.destination_path):
        debug(
            'Destination path "{}" does not exist, creating it.',
            gallery.destination_path,
        )
        os.mkdir(gallery.destination_path)

    generate_images(gallery)
    render_html_pages(gallery)
    copy_additional_static_files(gallery.destination_path)
    debug('Done.')

def generate_images(gallery: Gallery) -> None:
    for image in gallery.images:
        generate_image(image, gallery)
        generate_thumbnail(image, gallery)
        image.caption = load_caption(image)

def render_html_pages(gallery: Gallery) -> None:
    for image in gallery.images:
        render_html_page(gallery, image)
    render_html_index_page(gallery)

def render_html_index_page(gallery: Gallery) -> None:
    """Create an HTML document of thumbnails that link to single image
    documents.
    """
    context = {'gallery': gallery}
    render_html_to_file('index', context, gallery.destination_path, 'index')

def copy_additional_static_files(destination_path: str) -> None:
    if not os.path.exists(PATH_STATIC):
        debug(
            'Path "{}", does not exist; not copying any static files.',
            PATH_STATIC,
        )
        return

    filenames = list(sorted(os.listdir(PATH_STATIC)))
    if not filenames:
        debug('No static files to copy.')
    for filename in filenames:
        debug('Copying static file "{}" ...', filename)
        shutil.copy(
            os.path.join(PATH_STATIC, filename),
            os.path.join(destination_path, filename),
        )

def window(
    images: Iterable[Image],
) -> Iterator[Tuple[Optional[Image], Image, Optional[Image]]]:
    """Return a sliding window over the images.

    Returns a triple. Its first and third element can be `None`, but the
    second element never is.
    """
    n = 3
    padded_list: List[Optional[Image]] = list(images)
    padded_list.insert(0, None)
    padded_list.append(None)

    it = iter(padded_list)
    result = tuple(islice(it, n))

    if len(result) == n:
        yield result  # type: ignore

    for elem in it:
        result = result[1:] + (elem,)
        yield result  # type: ignore

@dataclass
class Image:
    full_filename: str
    path: str
    filename: str
    thumbnail_filename: str
    page_name: str
    previous_image: Optional[Image] = None
    next_image: Optional[Image] = None

def create_image(full_filename: str) -> Image:
    path, filename = os.path.split(full_filename)
    basename, extension = os.path.splitext(filename)
    thumbnail_filename = f'{basename}_t{extension}'

    return Image(
        full_filename=full_filename,
        path=path,
        filename=filename,
        thumbnail_filename=thumbnail_filename,
        page_name=basename,
    )

def generate_image(image: Image, gallery: Gallery) -> None:
    """Create a (optionally resized) copy of an image."""
    destination_filename = os.path.join(
        gallery.destination_path, image.filename
    )
    if gallery.resize:
        # Resize image.
        debug('Resizing image "{}" ...', image.full_filename)
        resize_image(
            image.full_filename,
            destination_filename,
            gallery.max_image_size,
        )
    else:
        # Copy image.
        debug('Copying image "{}" ...', image.full_filename)
        shutil.copy(image.full_filename, destination_filename)

def generate_thumbnail(image: Image, gallery: Gallery) -> None:
    """Create a preview of an image."""
    debug('Creating thumbnail "{}" ...', image.thumbnail_filename)
    destination_filename = os.path.join(
        gallery.destination_path, image.thumbnail_filename
    )
    resize_image(
        image.full_filename,
        destination_filename,
        gallery.max_thumbnail_size,
    )

def load_caption(image: Image) -> str:
    """Load image caption from file."""
    caption_filename = os.path.join(
        image.path, image.filename + IMAGE_CAPTION_EXTENSION
    )
    return _read_first_line(caption_filename)

def _read_first_line(filename: str) -> Optional[str]:
    """Read the first line from the specified file."""
    try:
        text = Path(filename).read_text(encoding='utf-8')
        first_line = text.splitlines()[0]
        return first_line.strip()
    except IOError:
        # File does not exist (OK) or cannot be read (not really OK).
        return None

def render_html_page(gallery: Gallery, image: Image) -> None:
    """Create an HTML document for a single image."""
    context = {'gallery': gallery, 'image': image}
    render_html_to_file(
        'view', context, gallery.destination_path, image.page_name
    )

# -------------------------------------------------------------------- #

def handle_duplicate_filenames(paths: Iterable[str]) -> None:
    duplicates = find_duplicate_filenames(paths)
    if not duplicates:
        return

    print('Found duplicate filenames:')
    for filename, paths in duplicates:
        print(f'  + "{filename}" appears in the following paths:')
        for path in paths:
            print(f'    - {path}')

    sys.exit('Clashing target filenames, aborting.')

def find_duplicate_filenames(
    paths: Iterable[str],
) -> List[Tuple[str, List[str]]]:
    d = defaultdict(list)
    for path in paths:
        key = os.path.basename(path).lower()
        d[key].append(path)

    return [item for item in d.items() if len(item[1]) > 1]
<|endoftext|>


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

from __future__ import absolute_import

from PyQt4 import QtCore, QtGui
from src.gui.qt.mainwindow import Ui_MainWindow

from src.core.estado.estado import Estado, TipoEstado
from src.core.gridworld.gridworld import GridWorld

try:
    _tr = QtCore.QString.fromUtf8
except AttributeError:
    _tr = lambda s: s

class MainWindow(QtGui.QMainWindow):
    u"""
    Clase heredada de QMainWindow encargada de mostrar la ventana principal de
    la aplicación.
    """
    def __init__(self):
        super(MainWindow, self).__init__()
        self.WMainWindow = Ui_MainWindow()
        self.WMainWindow.setupUi(self)
        self._initialize_window()

    def _initialize_window(self):
        u"""
        Configura y establece estado de los widgets en el cuadro de diálogo.
        """
        # Aspectos de la ventana principal
        self.setWindowIcon(QtGui.QIcon('img/96x96.png'))

        # Conexión de señales
        self._set_window_signals()

        #Cargar técnicas posibles
        tecnicas = ["ε-Greedy", "Softmax"]
        self.WMainWindow.cbQLTecnicas.clear()
        for tecnica in tecnicas:
            self.WMainWindow.cbQLTecnicas.addItem(_tr(tecnica))
            self.WMainWindow.cbQLTecnicas.addAction(QtGui.QAction(_tr(tecnica), self))

        # Cargar dimensiones posibles del GridWorld
        gw_dimensiones = ["6", "7", "8", "9", "10"]

        self.WMainWindow.cbGWDimension.clear()
        for dimension in gw_dimensiones:
            self.WMainWindow.cbGWDimension.addItem(_tr(dimension))
            self.WMainWindow.menuDimension.addAction(QtGui.QAction(_tr(dimension), self))

        # TODO: Refactorear sección

        u"""Establece la dimensión por defecto del GridWorld en 6x6"""
        self.SetDimension()

        u"""Cambia la dimensión del GridWorld según la opción activa en el ComboBox cbDWDimension"""
        QtCore.QObject.connect(self.WMainWindow.cbGWDimension, QtCore.SIGNAL("currentIndexChanged(QString)"),self.SetDimension)

    def SetDimension(self):
            u""" Configura el GridWorld"""

            cant_cuadrados =int(self.WMainWindow.cbGWDimension.currentText())
            ancho_cuadrado = 40

            ancho_gridworld = ancho_cuadrado * cant_cuadrados

            self.WMainWindow.GridWorld.setRowCount(cant_cuadrados)
            self.WMainWindow.GridWorld.setColumnCount(cant_cuadrados)
            self.WMainWindow.GridWorld.horizontalHeader().setDefaultSectionSize(ancho_cuadrado)
            self.WMainWindow.GridWorld.horizontalHeader().setResizeMode(QtGui.QHeaderView.Fixed)
            self.WMainWindow.GridWorld.verticalHeader().setDefaultSectionSize(ancho_cuadrado)
            self.WMainWindow.GridWorld.verticalHeader().setResizeMode(QtGui.QHeaderView.Fixed)
            self.WMainWindow.GridWorld.setCursor(QtCore.Qt.PointingHandCursor)
            ancho_contenedor = ancho_gridworld + self.WMainWindow.GridWorld.verticalHeader().width() + 1
            alto_contenedor = ancho_gridworld + self.WMainWindow.GridWorld.horizontalHeader().height() + 1
            self.WMainWindow.GridWorld.setFixedSize(ancho_contenedor, alto_contenedor)

            for fila in range(0, cant_cuadrados):
                for columna in range(0, cant_cuadrados):
                    elemento = QtGui.QTableWidgetItem("({0},{1})".format(fila, columna))
                    elemento.setFlags(QtCore.Qt.ItemIsEnabled)
                    self.WMainWindow.GridWorld.setItem(fila, columna, elemento)
        # ------------------------------------------------------------------

    def _set_window_signals(self):
        self.WMainWindow.actionAppSalir.triggered.connect(self.exit)

    def _inicializar_estados(self):
        # Identificadores de estado reservados
        # Ide = 0 (Estado Inicial)
        # Ide = 1 (Estado Intermedio)
        # Ide = 2 (Estado Final)
        # Ide = 3 (Agente)

        tipos_estados = []
        # Estado Inicial
        tipos_estados.append(TipoEstado(0, None, "Inicial", "I", None))
        # Estado Final
        tipos_estados.append(TipoEstado(2, None, "Final", "F", None))
        # Estado Agente
        tipos_estados.append(TipoEstado(3, None, "Agente", "A", None))
        # Estados Intermedios
        tipos_estados.append(TipoEstado(1, 100, "Excelente", "E", None))
        tipos_estados.append(TipoEstado(1, 50, "Bueno", "B", None))
        tipos_estados.append(TipoEstado(1, 10, "Malo", "M", None))
        tipos_estados.append(TipoEstado(1, 0, "Neutro", None, None))
        tipos_estados.append(TipoEstado(1, -100, "Pared", "P", None))

        # TODO: Inicializar todos los estados como Neutros

    def show_about_dialog(self):
        u"""
        Muestra el cuadro de diálogo Acerca de...
        """
        self.DAboutDialog = AboutDialog(self)
        self.DAboutDialog.show()

    def closeEvent(self, event):
        """
        Reimplementa el evento 'closeEvent' de la clase padre.

        @param event: Evento.
        """
        pass

    def exit(self):
        u"""
        Finaliza la ejecución de la aplicación.
        """
        self.close()
<commit_msg>Botones agregados a la interfaz<commit_after># -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'mainwindow.ui'
#
# Created: Thu Apr 04 23:05:32 2013
#      by: PyQt4 UI code generator 4.10
#
# WARNING! All changes made in this file will be lost!

from PyQt4 import QtCore, QtGui

try:
    _fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
    def _fromUtf8(s):
        return s

try:
    _encoding = QtGui.QApplication.UnicodeUTF8
    def _translate(context, text, disambig):
        return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
    def _translate(context, text, disambig):
        return QtGui.QApplication.translate(context, text, disambig)

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName(_fromUtf8("MainWindow"))
        MainWindow.resize(620, 408)
        self.centralWidget = QtGui.QWidget(MainWindow)
        self.centralWidget.setObjectName(_fromUtf8("centralWidget"))
        self.horizontalLayout_7 = QtGui.QHBoxLayout(self.centralWidget)
        self.horizontalLayout_7.setObjectName(_fromUtf8("horizontalLayout_7"))
        spacerItem = QtGui.QSpacerItem(20, 346, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
        self.horizontalLayout_7.addItem(spacerItem)
        spacerItem1 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
        self.horizontalLayout_7.addItem(spacerItem1)
        self.GridWorld = QtGui.QTableWidget(self.centralWidget)
        sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.GridWorld.sizePolicy().hasHeightForWidth())
        self.GridWorld.setSizePolicy(sizePolicy)
        self.GridWorld.setFocusPolicy(QtCore.Qt.ClickFocus)
        self.GridWorld.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.GridWorld.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.GridWorld.setAutoScroll(False)
        self.GridWorld.setTabKeyNavigation(False)
        self.GridWorld.setProperty("showDropIndicator", False)
        self.GridWorld.setDragDropOverwriteMode(False)
        self.GridWorld.setSelectionMode(QtGui.QAbstractItemView.NoSelection)
        self.GridWorld.setCornerButtonEnabled(False)
        self.GridWorld.setObjectName(_fromUtf8("GridWorld"))
        self.GridWorld.setColumnCount(0)
        self.GridWorld.setRowCount(0)
        self.horizontalLayout_7.addWidget(self.GridWorld)
        spacerItem2 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
        self.horizontalLayout_7.addItem(spacerItem2)
        self.verticalLayout_3 = QtGui.QVBoxLayout()
        self.verticalLayout_3.setObjectName(_fromUtf8("verticalLayout_3"))
        self.groupBox = QtGui.QGroupBox(self.centralWidget)
        self.groupBox.setObjectName(_fromUtf8("groupBox"))
        self.gridLayout = QtGui.QGridLayout(self.groupBox)
        self.gridLayout.setObjectName(_fromUtf8("gridLayout"))
        self.horizontalLayout = QtGui.QHBoxLayout()
        self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout"))
        self.label = QtGui.QLabel(self.groupBox)
        self.label.setObjectName(_fromUtf8("label"))
        self.horizontalLayout.addWidget(self.label)
        self.cbGWDimension = QtGui.QComboBox(self.groupBox)
        self.cbGWDimension.setObjectName(_fromUtf8("cbGWDimension"))
        self.horizontalLayout.addWidget(self.cbGWDimension)
        self.gridLayout.addLayout(self.horizontalLayout, 0, 0, 1, 1)
        self.verticalLayout_3.addWidget(self.groupBox)
        self.groupBox_3 = QtGui.QGroupBox(self.centralWidget)
        self.groupBox_3.setObjectName(_fromUtf8("groupBox_3"))
        self.verticalLayout = QtGui.QVBoxLayout(self.groupBox_3)
        self.verticalLayout.setObjectName(_fromUtf8("verticalLayout"))
        self.horizontalLayout_2 = QtGui.QHBoxLayout()
        self.horizontalLayout_2.setObjectName(_fromUtf8("horizontalLayout_2"))
        self.label_4 = QtGui.QLabel(self.groupBox_3)
        self.label_4.setObjectName(_fromUtf8("label_4"))
        self.horizontalLayout_2.addWidget(self.label_4)
        self.sbQLGamma = QtGui.QDoubleSpinBox(self.groupBox_3)
        self.sbQLGamma.setObjectName(_fromUtf8("sbQLGamma"))
        self.horizontalLayout_2.addWidget(self.sbQLGamma)
        self.verticalLayout.addLayout(self.horizontalLayout_2)
        self.horizontalLayout_3 = QtGui.QHBoxLayout()
        self.horizontalLayout_3.setObjectName(_fromUtf8("horizontalLayout_3"))
        self.label_2 = QtGui.QLabel(self.groupBox_3)
        self.label_2.setObjectName(_fromUtf8("label_2"))
        self.horizontalLayout_3.addWidget(self.label_2)
        self.cbQLTecnicas = QtGui.QComboBox(self.groupBox_3)
        self.cbQLTecnicas.setObjectName(_fromUtf8("cbQLTecnicas"))
        self.horizontalLayout_3.addWidget(self.cbQLTecnicas)
        self.verticalLayout.addLayout(self.horizontalLayout_3)
        self.horizontalLayout_4 = QtGui.QHBoxLayout()
        self.horizontalLayout_4.setObjectName(_fromUtf8("horizontalLayout_4"))
        self.label_3 = QtGui.QLabel(self.groupBox_3)
        self.label_3.setObjectName(_fromUtf8("label_3"))
        self.horizontalLayout_4.addWidget(self.label_3)
        self.sbQLAlpha = QtGui.QDoubleSpinBox(self.groupBox_3)
        self.sbQLAlpha.setObjectName(_fromUtf8("sbQLAlpha"))
        self.horizontalLayout_4.addWidget(self.sbQLAlpha)
        self.verticalLayout.addLayout(self.horizontalLayout_4)
        self.horizontalLayout_5 = QtGui.QHBoxLayout()
        self.horizontalLayout_5.setObjectName(_fromUtf8("horizontalLayout_5"))
        self.label_5 = QtGui.QLabel(self.groupBox_3)
        self.label_5.setObjectName(_fromUtf8("label_5"))
        self.horizontalLayout_5.addWidget(self.label_5)
        self.sbQLEpsilon = QtGui.QDoubleSpinBox(self.groupBox_3)
        self.sbQLEpsilon.setObjectName(_fromUtf8("sbQLEpsilon"))
        self.horizontalLayout_5.addWidget(self.sbQLEpsilon)
        self.verticalLayout.addLayout(self.horizontalLayout_5)
        self.horizontalLayout_6 = QtGui.QHBoxLayout()
        self.horizontalLayout_6.setObjectName(_fromUtf8("horizontalLayout_6"))
        self.label_6 = QtGui.QLabel(self.groupBox_3)
        self.label_6.setObjectName(_fromUtf8("label_6"))
        self.horizontalLayout_6.addWidget(self.label_6)
        self.sbQLTau = QtGui.QDoubleSpinBox(self.groupBox_3)
        self.sbQLTau.setObjectName(_fromUtf8("sbQLTau"))
        self.horizontalLayout_6.addWidget(self.sbQLTau)
        self.verticalLayout.addLayout(self.horizontalLayout_6)
        self.verticalLayout_3.addWidget(self.groupBox_3)
        self.verticalLayout_2 = QtGui.QVBoxLayout()
        self.verticalLayout_2.setObjectName(_fromUtf8("verticalLayout_2"))
        self.groupBox_2 = QtGui.QGroupBox(self.centralWidget)
        self.groupBox_2.setObjectName(_fromUtf8("groupBox_2"))
        self.gridLayout_3 = QtGui.QGridLayout(self.groupBox_2)
        self.gridLayout_3.setObjectName(_fromUtf8("gridLayout_3"))
        spacerItem3 = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
        self.gridLayout_3.addItem(spacerItem3, 5, 0, 1, 1)
        self.entrenar = QtGui.QPushButton(self.groupBox_2)
        self.entrenar.setObjectName(_fromUtf8("entrenar"))
        self.gridLayout_3.addWidget(self.entrenar, 2, 0, 1, 1)
        self.comenzar = QtGui.QPushButton(self.groupBox_2)
        self.comenzar.setObjectName(_fromUtf8("comenzar"))
        self.gridLayout_3.addWidget(self.comenzar, 3, 0, 1, 1)
        self.genValAleatorios = QtGui.QPushButton(self.groupBox_2)
        self.genValAleatorios.setObjectName(_fromUtf8("genValAleatorios"))
        self.gridLayout_3.addWidget(self.genValAleatorios, 0, 0, 1, 1)
        self.pushButton_4 = QtGui.QPushButton(self.groupBox_2)
        self.pushButton_4.setObjectName(_fromUtf8("pushButton_4"))
        self.gridLayout_3.addWidget(self.pushButton_4, 4, 0, 1, 1)
        self.verticalLayout_2.addWidget(self.groupBox_2)
        self.verticalLayout_3.addLayout(self.verticalLayout_2)
        self.horizontalLayout_7.addLayout(self.verticalLayout_3)
        MainWindow.setCentralWidget(self.centralWidget)
        self.menuBar = QtGui.QMenuBar(MainWindow)
        self.menuBar.setGeometry(QtCore.QRect(0, 0, 620, 21))
        self.menuBar.setObjectName(_fromUtf8("menuBar"))
        self.menuArchivo = QtGui.QMenu(self.menuBar)
        self.menuArchivo.setObjectName(_fromUtf8("menuArchivo"))
        self.menuEdicion = QtGui.QMenu(self.menuBar)
        self.menuEdicion.setObjectName(_fromUtf8("menuEdicion"))
        self.menuAyuda = QtGui.QMenu(self.menuBar)
        self.menuAyuda.setObjectName(_fromUtf8("menuAyuda"))
        self.menuGridWorld = QtGui.QMenu(self.menuBar)
        self.menuGridWorld.setObjectName(_fromUtf8("menuGridWorld"))
        self.menuDimension = QtGui.QMenu(self.menuGridWorld)
        self.menuDimension.setObjectName(_fromUtf8("menuDimension"))
        self.menuOperaciones = QtGui.QMenu(self.menuBar)
        self.menuOperaciones.setObjectName(_fromUtf8("menuOperaciones"))
        MainWindow.setMenuBar(self.menuBar)
        self.statusBar = QtGui.QStatusBar(MainWindow)
        self.statusBar.setObjectName(_fromUtf8("statusBar"))
        MainWindow.setStatusBar(self.statusBar)
        self.actionAppSalir = QtGui.QAction(MainWindow)
        self.actionAppSalir.setObjectName(_fromUtf8("actionAppSalir"))
        self.actionAcercaDe = QtGui.QAction(MainWindow)
        self.actionAcercaDe.setObjectName(_fromUtf8("actionAcercaDe"))
        self.actionGenerarValoresAleatorios = QtGui.QAction(MainWindow)
        self.actionGenerarValoresAleatorios.setObjectName(_fromUtf8("actionGenerarValoresAleatorios"))
        self.menuArchivo.addAction(self.actionAppSalir)
        self.menuAyuda.addAction(self.actionAcercaDe)
        self.menuGridWorld.addAction(self.menuDimension.menuAction())
        self.menuOperaciones.addAction(self.actionGenerarValoresAleatorios)
        self.menuBar.addAction(self.menuArchivo.menuAction())
        self.menuBar.addAction(self.menuEdicion.menuAction())
        self.menuBar.addAction(self.menuGridWorld.menuAction())
        self.menuBar.addAction(self.menuOperaciones.menuAction())
        self.menuBar.addAction(self.menuAyuda.menuAction())
        self.label.setBuddy(self.cbGWDimension)
        self.label_2.setBuddy(self.cbQLTecnicas)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow", None))
        self.groupBox.setTitle(_translate("MainWindow", "Grid World", None))
        self.label.setText(_translate("MainWindow", "Dimensión:", None))
        self.groupBox_3.setTitle(_translate("MainWindow", "Q-Learning", None))
        self.label_4.setText(_translate("MainWindow", "Gamma (Ɣ):", None))
        self.label_2.setText(_translate("MainWindow", "Técnica de aprendizaje:", None))
        self.label_3.setText(_translate("MainWindow", "Alfa (ɑ):", None))
        self.label_5.setText(_translate("MainWindow", "Epsilon (ɛ):", None))
        self.label_6.setText(_translate("MainWindow", "Tau (τ):", None))
        self.groupBox_2.setTitle(_translate("MainWindow", "Operaciones", None))
        self.entrenar.setText(_translate("MainWindow", "Entrenar", None))
        self.comenzar.setText(_translate("MainWindow", "Comenzar", None))
        self.genValAleatorios.setText(_translate("MainWindow", "Generar valores aleatorios", None))
        self.pushButton_4.setText(_translate("MainWindow", "Terminar", None))
        self.menuArchivo.setTitle(_translate("MainWindow", "&Archivo", None))
        self.menuEdicion.setTitle(_translate("MainWindow", "&Edición", None))
        self.menuAyuda.setTitle(_translate("MainWindow", "Ay&uda", None))
        self.menuGridWorld.setTitle(_translate("MainWindow", "&Grid World", None))
        self.menuDimension.setTitle(_translate("MainWindow", "Dimensión", None))
        self.menuOperaciones.setTitle(_translate("MainWindow", "&Operaciones", None))
        self.actionAppSalir.setText(_translate("MainWindow", "Salir", None))
        self.actionAcercaDe.setText(_translate("MainWindow", "Acerca de...", None))
        self.actionGenerarValoresAleatorios.setText(_translate("MainWindow", "Generar valores aleatorios", None))

<|endoftext|>


<commit_before>""" test doing things with keys/signatures/etc
"""
import pytest

import os
import warnings

from pgpy import PGPKey
from pgpy import PGPMessage
from pgpy import PGPSignature

from pgpy.errors import PGPError
from pgpy.constants import CompressionAlgorithm
from pgpy.constants import SignatureType

class TestPGPMessage(object):
    def test_new_message_zip(self, lit, gpg_print):
        msg = PGPMessage.new(lit)

        assert msg.type == 'compressed'
        assert msg.message.decode('latin-1') == 'This is stored, literally\!\n\n'

        with open('tests/testdata/cmsg.asc', 'w') as litf:
            litf.write(str(msg))

        assert msg.message.decode('latin-1') == gpg_print('cmsg.asc')

        os.remove('tests/testdata/cmsg.asc')

    def test_new_message_zlib(self, lit, gpg_print):
        msg = PGPMessage.new(lit, compression=CompressionAlgorithm.ZLIB)

        assert msg.type == 'compressed'
        assert msg.message.decode('latin-1') == 'This is stored, literally\!\n\n'

        with open('tests/testdata/cmsg.asc', 'w') as litf:
            litf.write(str(msg))

        assert msg.message.decode('latin-1') == gpg_print('cmsg.asc')

        os.remove('tests/testdata/cmsg.asc')

    def test_new_message_bz2(self, lit, gpg_print):
        msg = PGPMessage.new(lit, compression=CompressionAlgorithm.BZ2)

        assert msg.type == 'compressed'
        assert msg.message.decode('latin-1') == 'This is stored, literally\!\n\n'

        with open('tests/testdata/cmsg.asc', 'w') as litf:
            litf.write(str(msg))

        assert msg.message.decode('latin-1') == gpg_print('cmsg.asc')

        os.remove('tests/testdata/cmsg.asc')

    def test_new_message_nocomp(self, lit, gpg_print):
        msg = PGPMessage.new(lit, compress=False)

        assert msg.type == 'literal'
        assert msg.message.decode('latin-1') == 'This is stored, literally\!\n\n'

        with open('tests/testdata/lmsg.asc', 'w') as litf:
            litf.write(str(msg))

        assert msg.message.decode('latin-1') == gpg_print('lmsg.asc')

        os.remove('tests/testdata/lmsg.asc')

    def test_message_change_compalg(self, lit, gpg_print):
        # defaults to ZIP
        msg = PGPMessage.new(lit)
        assert msg.type == 'compressed'
        assert msg.message.decode('latin-1') == 'This is stored, literally\!\n\n'

        # change to ZLIB
        msg._contents[0].calg = CompressionAlgorithm.ZLIB
        assert msg.type == 'compressed'
        assert msg.message.decode('latin-1') == 'This is stored, literally\!\n\n'

        with open('tests/testdata/cmsg.asc', 'w') as litf:
            litf.write(str(msg))

        assert msg.message.decode('latin-1') == gpg_print('cmsg.asc')

        os.remove('tests/testdata/cmsg.asc')

    def test_decrypt_passphrase_message(self, passmessage):
        msg = PGPMessage()
        msg.parse(passmessage)

        decmsg = msg.decrypt("QwertyUiop")

        assert isinstance(decmsg, PGPMessage)
        assert decmsg.message == bytearray(b"This is stored, literally\\!\n\n")

    def test_encrypt_passphrase_message(self, lit, gpg_decrypt):
        msg = PGPMessage.new(lit)
        msg.encrypt("QwertyUiop")

        assert msg.type == 'encrypted'

        with open('tests/testdata/semsg.asc', 'w') as litf:
            litf.write(str(msg))

        # decrypt with PGPy
        decmsg = msg.decrypt("QwertyUiop")
        assert isinstance(decmsg, PGPMessage)
        assert decmsg.type == 'compressed'
        assert decmsg.message == bytearray(b"This is stored, literally\\!\n\n")

        # decrypt with GPG
        assert gpg_decrypt('./semsg.asc', "QwertyUiop") == 'This is stored, literally\!\n\n'

        os.remove('tests/testdata/semsg.asc')

class TestPGPKey(object):
    def test_unlock_encrsakey(self, encrsakey, rsakey):
        ekey = PGPKey()
        ekey.parse(encrsakey)
        rkey = PGPKey()
        rkey.parse(rsakey)

        assert ekey.is_protected
        assert not ekey.is_unlocked
        assert not rkey.is_protected

        with pytest.raises(PGPError):
            ekey.sign('tests/testdata/lit')

        with ekey.unlock('QwertyUiop'), warnings.catch_warnings():
            warnings.simplefilter('ignore')
            assert ekey.is_unlocked
            sig = ekey.sign('tests/testdata/lit')
            assert ekey.verify('tests/testdata/lit', sig)
            assert rkey.verify('tests/testdata/lit', sig)

        assert not ekey.is_unlocked

    def test_verify_detach(self, sigf):
        # test verifying signatures in tests/testdata/signatures
        key = PGPKey()
        key.parse(sigf + '.key.asc')
        sig = PGPSignature()
        sig.parse(sigf + '.sig.asc')
        sigv = key.verify(sigf + '.subj', sig)

        assert sigv

    def test_verify_cleartext(self, ctmessage, rsakey, dsakey):
        rsa = PGPKey()
        rsa.parse(rsakey)
        dsa = PGPKey()
        dsa.parse(dsakey)

        ctmsg = PGPMessage()
        ctmsg.parse(ctmessage)

        with warnings.catch_warnings():
            warnings.simplefilter('ignore')
            assert rsa.verify(ctmsg)
            assert dsa.verify(ctmsg)

    def test_verify_onepass_signed_message(self, rsakey, dsakey):
        opmsg = PGPMessage()
        opmsg.parse('tests/testdata/messages/message.signed_rsa.signed_dsa.asc')
        rkey = PGPKey()
        rkey.parse(rsakey)
        dkey = PGPKey()
        dkey.parse(dsakey)

        with warnings.catch_warnings():
            warnings.simplefilter('ignore')
            assert rkey.verify(opmsg)
            assert dkey.verify(opmsg)

    def test_verify_signed_message(self, rsakey):
        smsg = PGPMessage()
        smsg.parse('tests/testdata/blocks/message.signed.asc')
        rkey = PGPKey()
        rkey.parse(rsakey)

        with warnings.catch_warnings():
            warnings.simplefilter('ignore')
            assert rkey.verify(smsg)

    def test_verify_key_selfsigs(self, pubkey):
        k = PGPKey()
        k.parse(pubkey)

        with warnings.catch_warnings():
            warnings.simplefilter('ignore')

            # verify user id(s)
            for uid in k.userids:
                assert k.verify(uid)

            # verify user attribute(s)
            for ua in k.userattributes:
                assert k.verify(ua)

            # verify subkey binding signatures
            for sk in k.subkeys.values():
                assert k.verify(sk)

    def test_verify_key_allsigs(self, pubkey):
        k = PGPKey()
        k.parse(pubkey)

        with warnings.catch_warnings():
            warnings.simplefilter('ignore')
            sv = k.verify(k)

        assert len(sv._subjects) == 13
        _svtypes = [ s.signature.type for s in sv._subjects ]
        assert SignatureType.CertRevocation in _svtypes
        assert SignatureType.DirectlyOnKey in _svtypes
        assert SignatureType.KeyRevocation in _svtypes
        assert SignatureType.Positive_Cert in _svtypes
        assert SignatureType.Subkey_Binding in _svtypes
        assert SignatureType.PrimaryKey_Binding in _svtypes
        assert SignatureType.SubkeyRevocation in _svtypes
        assert sv

    def test_verify_wrongkey(self):
        wrongkey = PGPKey()
        wrongkey.parse('tests/testdata/signatures/aptapproval-test.key.asc')

        sig = PGPSignature()
        sig.parse('tests/testdata/signatures/debian-sid.sig.asc')

        with pytest.raises(PGPError):
            wrongkey.verify('tests/testdata/signatures/debian-sid.subj', sig)

    def test_verify_invalid(self, rsakey):
        rkey = PGPKey()
        rkey.parse(rsakey)

        with warnings.catch_warnings():
            warnings.simplefilter('ignore')
            sig = rkey.sign('tests/testdata/lit')
            assert not rkey.verify('tests/testdata/lit2', sig)

    def test_sign_rsa_bindoc(self, rsakey, gpg_verify):
        # test signing binary documents with RSA
        key = PGPKey()
        key.parse(rsakey)

        with warnings.catch_warnings():
            warnings.simplefilter('ignore')
            sig = key.sign('tests/testdata/lit')

        with open('tests/testdata/lit.sig', 'w') as sigf:
            sigf.write(str(sig))

        # verify with PGPy
        with warnings.catch_warnings():
            warnings.simplefilter('ignore')
            assert key.verify('tests/testdata/lit', sig)

        # verify with GPG
        assert gpg_verify('./lit', './lit.sig')

        os.remove('tests/testdata/lit.sig')

    def test_sign_dsa_bindoc(self, dsakey, gpg_verify):
        # test signing binary documents with DSA
        key = PGPKey()
        key.parse(dsakey)

        with warnings.catch_warnings():
            warnings.simplefilter('ignore')
            sig = key.sign('tests/testdata/lit')

        with open('tests/testdata/lit.sig', 'w') as sigf:
            sigf.write(str(sig))

        # verify with PGPy
        with warnings.catch_warnings():
            warnings.simplefilter('ignore')
            assert key.verify('tests/testdata/lit', sig)

        # verify with GPG
        assert gpg_verify('./lit', './lit.sig')

        os.remove('tests/testdata/lit.sig')

    def test_sign_rsa_cleartext(self, rsakey, gpg_verify):
        key = PGPKey()
        key.parse(rsakey)

        with warnings.catch_warnings():
            warnings.simplefilter('ignore')
            ctsmsg = key.sign('tests/testdata/lit', inline=True)

        assert isinstance(ctsmsg, PGPMessage)
        assert ctsmsg.type == 'cleartext'

        with open('tests/testdata/lit.asc', 'w') as isigf:
            isigf.write(str(ctsmsg))

        # verify with PGPy
        with warnings.catch_warnings():
            warnings.simplefilter('ignore')
            assert key.verify(ctsmsg)

        # verify with GPG
        assert gpg_verify('./lit.asc')

        os.remove('tests/testdata/lit.asc')

    def test_sign_dsa_cleartext(self, dsakey, gpg_verify):
        key = PGPKey()
        key.parse(dsakey)

        with warnings.catch_warnings():
            warnings.simplefilter('ignore')
            ctsmsg = key.sign('tests/testdata/lit', inline=True)

        assert isinstance(ctsmsg, PGPMessage)
        assert ctsmsg.type == 'cleartext'

        with open('tests/testdata/lit.asc', 'w') as isigf:
            isigf.write(str(ctsmsg))

        # verify with PGPy
        with warnings.catch_warnings():
            warnings.simplefilter('ignore')
            assert key.verify(ctsmsg)

        # verify with GPG
        assert gpg_verify('./lit.asc')

        os.remove('tests/testdata/lit.asc')

    def test_sign_rsa_dsa_cleartext(self, rsakey, dsakey, gpg_verify):
        rkey = PGPKey()
        rkey.parse(rsakey)
        dkey = PGPKey()
        dkey.parse(dsakey)

        with warnings.catch_warnings():
            warnings.simplefilter('ignore')
            ctsmsg = rkey.sign('tests/testdata/lit_de', inline=True)
            ctsmsg = dkey.sign(ctsmsg, inline=True)

        with open('tests/testdata/lit_de.asc', 'w') as isigf:
            isigf.write(str(ctsmsg))

        # verify with PGPy
        with warnings.catch_warnings():
            warnings.simplefilter('ignore')
            assert rkey.verify(ctsmsg)
            assert dkey.verify(ctsmsg)

        # verify with GPG
        assert gpg_verify('./lit_de.asc')

        os.remove('tests/testdata/lit_de.asc')

    def test_decrypt_rsa_message(self, rsakey, rsamessage):
        key = PGPKey()
        key.parse(rsakey)

        msg = PGPMessage()
        msg.parse(rsamessage)

        with warnings.catch_warnings():
            warnings.simplefilter('ignore')
            decmsg = key.decrypt(msg)

        assert isinstance(decmsg, PGPMessage)
        assert decmsg.message == bytearray(b"This is stored, literally\\!\n\n")

    def test_encrypt_rsa_message(self, rsakey, gpg_decrypt):
        key = PGPKey()
        key.parse(rsakey)

        msg = PGPMessage.new('tests/testdata/lit')

        with warnings.catch_warnings():
            warnings.simplefilter('ignore')
            encmsg = key.encrypt(msg)

        assert isinstance(encmsg, PGPMessage)
        assert encmsg.is_encrypted

        with open('tests/testdata/aemsg.asc', 'w') as litf:
            litf.write(str(encmsg))

        # decrypt with PGPy
        with warnings.catch_warnings():
            warnings.simplefilter('ignore')
            decmsg = key.decrypt(encmsg)
        assert isinstance(decmsg, PGPMessage)
        assert not decmsg.is_encrypted
        assert decmsg.message == bytearray(b'This is stored, literally\!\n\n')

        # decrypt with GPG
        assert gpg_decrypt('./aemsg.asc') == 'This is stored, literally\!\n\n'

        os.remove('tests/testdata/aemsg.asc')

    def test_warnings(self):
        pytest.skip("not implemented yet")
<commit_msg>stubbed out tests for #114 [skip ci]<commit_after>""" test doing things with keys/signatures/etc
"""
import pytest

import os
import warnings

from pgpy import PGPKey
from pgpy import PGPMessage
from pgpy import PGPSignature

from pgpy.errors import PGPError
from pgpy.constants import CompressionAlgorithm
from pgpy.constants import SignatureType

class TestPGPMessage(object):
    def test_new_message_zip(self, lit, gpg_print):
        msg = PGPMessage.new(lit)

        assert msg.type == 'compressed'
        assert msg.message.decode('latin-1') == 'This is stored, literally\!\n\n'

        with open('tests/testdata/cmsg.asc', 'w') as litf:
            litf.write(str(msg))

        assert msg.message.decode('latin-1') == gpg_print('cmsg.asc')

        os.remove('tests/testdata/cmsg.asc')

    def test_new_message_zlib(self, lit, gpg_print):
        msg = PGPMessage.new(lit, compression=CompressionAlgorithm.ZLIB)

        assert msg.type == 'compressed'
        assert msg.message.decode('latin-1') == 'This is stored, literally\!\n\n'

        with open('tests/testdata/cmsg.asc', 'w') as litf:
            litf.write(str(msg))

        assert msg.message.decode('latin-1') == gpg_print('cmsg.asc')

        os.remove('tests/testdata/cmsg.asc')

    def test_new_message_bz2(self, lit, gpg_print):
        msg = PGPMessage.new(lit, compression=CompressionAlgorithm.BZ2)

        assert msg.type == 'compressed'
        assert msg.message.decode('latin-1') == 'This is stored, literally\!\n\n'

        with open('tests/testdata/cmsg.asc', 'w') as litf:
            litf.write(str(msg))

        assert msg.message.decode('latin-1') == gpg_print('cmsg.asc')

        os.remove('tests/testdata/cmsg.asc')

    def test_new_message_nocomp(self, lit, gpg_print):
        msg = PGPMessage.new(lit, compress=False)

        assert msg.type == 'literal'
        assert msg.message.decode('latin-1') == 'This is stored, literally\!\n\n'

        with open('tests/testdata/lmsg.asc', 'w') as litf:
            litf.write(str(msg))

        assert msg.message.decode('latin-1') == gpg_print('lmsg.asc')

        os.remove('tests/testdata/lmsg.asc')

    def test_message_change_compalg(self, lit, gpg_print):
        # defaults to ZIP
        msg = PGPMessage.new(lit)
        assert msg.type == 'compressed'
        assert msg.message.decode('latin-1') == 'This is stored, literally\!\n\n'

        # change to ZLIB
        msg._contents[0].calg = CompressionAlgorithm.ZLIB
        assert msg.type == 'compressed'
        assert msg.message.decode('latin-1') == 'This is stored, literally\!\n\n'

        with open('tests/testdata/cmsg.asc', 'w') as litf:
            litf.write(str(msg))

        assert msg.message.decode('latin-1') == gpg_print('cmsg.asc')

        os.remove('tests/testdata/cmsg.asc')

    def test_decrypt_passphrase_message(self, passmessage):
        msg = PGPMessage()
        msg.parse(passmessage)

        decmsg = msg.decrypt("QwertyUiop")

        assert isinstance(decmsg, PGPMessage)
        assert decmsg.message == bytearray(b"This is stored, literally\\!\n\n")

    def test_encrypt_passphrase_message(self, lit, gpg_decrypt):
        msg = PGPMessage.new(lit)
        msg.encrypt("QwertyUiop")

        assert msg.type == 'encrypted'

        with open('tests/testdata/semsg.asc', 'w') as litf:
            litf.write(str(msg))

        # decrypt with PGPy
        decmsg = msg.decrypt("QwertyUiop")
        assert isinstance(decmsg, PGPMessage)
        assert decmsg.type == 'compressed'
        assert decmsg.message == bytearray(b"This is stored, literally\\!\n\n")

        # decrypt with GPG
        assert gpg_decrypt('./semsg.asc', "QwertyUiop") == 'This is stored, literally\!\n\n'

        os.remove('tests/testdata/semsg.asc')

    def test_encrypt_passphrase_add_passphrase(self, lit, gpg_decrypt):
        pytest.skip("not implemented yet")

class TestPGPKey(object):
    def test_unlock_encrsakey(self, encrsakey, rsakey):
        ekey = PGPKey()
        ekey.parse(encrsakey)
        rkey = PGPKey()
        rkey.parse(rsakey)

        assert ekey.is_protected
        assert not ekey.is_unlocked
        assert not rkey.is_protected

        with pytest.raises(PGPError):
            ekey.sign('tests/testdata/lit')

        with ekey.unlock('QwertyUiop'), warnings.catch_warnings():
            warnings.simplefilter('ignore')
            assert ekey.is_unlocked
            sig = ekey.sign('tests/testdata/lit')
            assert ekey.verify('tests/testdata/lit', sig)
            assert rkey.verify('tests/testdata/lit', sig)

        assert not ekey.is_unlocked

    def test_verify_detach(self, sigf):
        # test verifying signatures in tests/testdata/signatures
        key = PGPKey()
        key.parse(sigf + '.key.asc')
        sig = PGPSignature()
        sig.parse(sigf + '.sig.asc')
        sigv = key.verify(sigf + '.subj', sig)

        assert sigv

    def test_verify_cleartext(self, ctmessage, rsakey, dsakey):
        rsa = PGPKey()
        rsa.parse(rsakey)
        dsa = PGPKey()
        dsa.parse(dsakey)

        ctmsg = PGPMessage()
        ctmsg.parse(ctmessage)

        with warnings.catch_warnings():
            warnings.simplefilter('ignore')
            assert rsa.verify(ctmsg)
            assert dsa.verify(ctmsg)

    def test_verify_onepass_signed_message(self, rsakey, dsakey):
        opmsg = PGPMessage()
        opmsg.parse('tests/testdata/messages/message.signed_rsa.signed_dsa.asc')
        rkey = PGPKey()
        rkey.parse(rsakey)
        dkey = PGPKey()
        dkey.parse(dsakey)

        with warnings.catch_warnings():
            warnings.simplefilter('ignore')
            assert rkey.verify(opmsg)
            assert dkey.verify(opmsg)

    def test_verify_signed_message(self, rsakey):
        smsg = PGPMessage()
        smsg.parse('tests/testdata/blocks/message.signed.asc')
        rkey = PGPKey()
        rkey.parse(rsakey)

        with warnings.catch_warnings():
            warnings.simplefilter('ignore')
            assert rkey.verify(smsg)

    def test_verify_key_selfsigs(self, pubkey):
        k = PGPKey()
        k.parse(pubkey)

        with warnings.catch_warnings():
            warnings.simplefilter('ignore')

            # verify user id(s)
            for uid in k.userids:
                assert k.verify(uid)

            # verify user attribute(s)
            for ua in k.userattributes:
                assert k.verify(ua)

            # verify subkey binding signatures
            for sk in k.subkeys.values():
                assert k.verify(sk)

    def test_verify_key_allsigs(self, pubkey):
        k = PGPKey()
        k.parse(pubkey)

        with warnings.catch_warnings():
            warnings.simplefilter('ignore')
            sv = k.verify(k)

        assert len(sv._subjects) == 13
        _svtypes = [ s.signature.type for s in sv._subjects ]
        assert SignatureType.CertRevocation in _svtypes
        assert SignatureType.DirectlyOnKey in _svtypes
        assert SignatureType.KeyRevocation in _svtypes
        assert SignatureType.Positive_Cert in _svtypes
        assert SignatureType.Subkey_Binding in _svtypes
        assert SignatureType.PrimaryKey_Binding in _svtypes
        assert SignatureType.SubkeyRevocation in _svtypes
        assert sv

    def test_verify_wrongkey(self):
        wrongkey = PGPKey()
        wrongkey.parse('tests/testdata/signatures/aptapproval-test.key.asc')

        sig = PGPSignature()
        sig.parse('tests/testdata/signatures/debian-sid.sig.asc')

        with pytest.raises(PGPError):
            wrongkey.verify('tests/testdata/signatures/debian-sid.subj', sig)

    def test_verify_invalid(self, rsakey):
        rkey = PGPKey()
        rkey.parse(rsakey)

        with warnings.catch_warnings():
            warnings.simplefilter('ignore')
            sig = rkey.sign('tests/testdata/lit')
            assert not rkey.verify('tests/testdata/lit2', sig)

    def test_sign_rsa_bindoc(self, rsakey, gpg_verify):
        # test signing binary documents with RSA
        key = PGPKey()
        key.parse(rsakey)

        with warnings.catch_warnings():
            warnings.simplefilter('ignore')
            sig = key.sign('tests/testdata/lit')

        with open('tests/testdata/lit.sig', 'w') as sigf:
            sigf.write(str(sig))

        # verify with PGPy
        with warnings.catch_warnings():
            warnings.simplefilter('ignore')
            assert key.verify('tests/testdata/lit', sig)

        # verify with GPG
        assert gpg_verify('./lit', './lit.sig')

        os.remove('tests/testdata/lit.sig')

    def test_sign_dsa_bindoc(self, dsakey, gpg_verify):
        # test signing binary documents with DSA
        key = PGPKey()
        key.parse(dsakey)

        with warnings.catch_warnings():
            warnings.simplefilter('ignore')
            sig = key.sign('tests/testdata/lit')

        with open('tests/testdata/lit.sig', 'w') as sigf:
            sigf.write(str(sig))

        # verify with PGPy
        with warnings.catch_warnings():
            warnings.simplefilter('ignore')
            assert key.verify('tests/testdata/lit', sig)

        # verify with GPG
        assert gpg_verify('./lit', './lit.sig')

        os.remove('tests/testdata/lit.sig')

    def test_sign_rsa_cleartext(self, rsakey, gpg_verify):
        key = PGPKey()
        key.parse(rsakey)

        with warnings.catch_warnings():
            warnings.simplefilter('ignore')
            ctsmsg = key.sign('tests/testdata/lit', inline=True)

        assert isinstance(ctsmsg, PGPMessage)
        assert ctsmsg.type == 'cleartext'

        with open('tests/testdata/lit.asc', 'w') as isigf:
            isigf.write(str(ctsmsg))

        # verify with PGPy
        with warnings.catch_warnings():
            warnings.simplefilter('ignore')
            assert key.verify(ctsmsg)

        # verify with GPG
        assert gpg_verify('./lit.asc')

        os.remove('tests/testdata/lit.asc')

    def test_sign_dsa_cleartext(self, dsakey, gpg_verify):
        key = PGPKey()
        key.parse(dsakey)

        with warnings.catch_warnings():
            warnings.simplefilter('ignore')
            ctsmsg = key.sign('tests/testdata/lit', inline=True)

        assert isinstance(ctsmsg, PGPMessage)
        assert ctsmsg.type == 'cleartext'

        with open('tests/testdata/lit.asc', 'w') as isigf:
            isigf.write(str(ctsmsg))

        # verify with PGPy
        with warnings.catch_warnings():
            warnings.simplefilter('ignore')
            assert key.verify(ctsmsg)

        # verify with GPG
        assert gpg_verify('./lit.asc')

        os.remove('tests/testdata/lit.asc')

    def test_sign_rsa_dsa_cleartext(self, rsakey, dsakey, gpg_verify):
        rkey = PGPKey()
        rkey.parse(rsakey)
        dkey = PGPKey()
        dkey.parse(dsakey)

        with warnings.catch_warnings():
            warnings.simplefilter('ignore')
            ctsmsg = rkey.sign('tests/testdata/lit_de', inline=True)
            ctsmsg = dkey.sign(ctsmsg, inline=True)

        with open('tests/testdata/lit_de.asc', 'w') as isigf:
            isigf.write(str(ctsmsg))

        # verify with PGPy
        with warnings.catch_warnings():
            warnings.simplefilter('ignore')
            assert rkey.verify(ctsmsg)
            assert dkey.verify(ctsmsg)

        # verify with GPG
        assert gpg_verify('./lit_de.asc')

        os.remove('tests/testdata/lit_de.asc')

    def test_sign_message(self, rsakey, dsakey, gpg_verify):
        pytest.skip("not implemented yet")

    def test_onepass_sign_message(self, rsakey, dsakey, gpg_verify):
        pytest.skip("not implemented yet")

    def test_decrypt_rsa_message(self, rsakey, rsamessage):
        key = PGPKey()
        key.parse(rsakey)

        msg = PGPMessage()
        msg.parse(rsamessage)

        with warnings.catch_warnings():
            warnings.simplefilter('ignore')
            decmsg = key.decrypt(msg)

        assert isinstance(decmsg, PGPMessage)
        assert decmsg.message == bytearray(b"This is stored, literally\\!\n\n")

    def test_encrypt_rsa_message(self, rsakey, gpg_decrypt):
        key = PGPKey()
        key.parse(rsakey)

        msg = PGPMessage.new('tests/testdata/lit')

        with warnings.catch_warnings():
            warnings.simplefilter('ignore')
            encmsg = key.encrypt(msg)

        assert isinstance(encmsg, PGPMessage)
        assert encmsg.is_encrypted

        with open('tests/testdata/aemsg.asc', 'w') as litf:
            litf.write(str(encmsg))

        # decrypt with PGPy
        with warnings.catch_warnings():
            warnings.simplefilter('ignore')
            decmsg = key.decrypt(encmsg)
        assert isinstance(decmsg, PGPMessage)
        assert not decmsg.is_encrypted
        assert decmsg.message == bytearray(b'This is stored, literally\!\n\n')

        # decrypt with GPG
        assert gpg_decrypt('./aemsg.asc') == 'This is stored, literally\!\n\n'

        os.remove('tests/testdata/aemsg.asc')

    def test_encrypt_rsa_add_recipient(self, rsakey, gpg_decrypt):
        pytest.skip("not implemented yet")

    def test_warnings(self):
        pytest.skip("not implemented yet")
<|endoftext|>