<commit_before>"""
Middleware to automatically set the schema (namespace).

if request.user.is_superuser, then look for a ?schema=XXX and set the schema to that.

Otherwise, set the schema to the one associated with the logged in user.

"""
from django.core.exceptions import ObjectDoesNotExist
from django.contrib import messages
from django.utils.translation import ugettext as _

from models import Schema

def activate_schema(request, available_schemata):
    if '__schema' in request.GET:
        request.session['schema'] = request.GET['__schema']
    if 'schema' in request.session:
        try:
            available_schemata.get(pk=request.session['schema']).activate()
        except ObjectDoesNotExist:
            messages.add_message(request, messages.WARNING,
                _(u'Unable to find Schema matching query: %s' % request.session['schema'])
            )
            request.session.pop('schema')
        else:
            return None

class SchemaMiddleware:
    def process_request(self, request):
        if request.user.is_anonymous():
            return None
        if request.user.is_superuser:
            available_schemata = Schema.objects
        elif request.user.schemata.exists():
            available_schemata = request.user.schemata

        activate_schema(request, available_schemata)

    def process_template_response(self, request, response):
        if request.user.is_anonymous():
            return response

        if request.user.is_superuser:
            available_schemata = Schema.objects.all()
        elif request.user.schemata.exists():
            available_schemata = request.user.schemata.all()
        else:
            # No schemata available for this user?
            available_schemata = Schema.objects.none()

        response.context_data['schemata'] = available_schemata

        if 'schema' in request.session:
            response.context_data['selected_schema'] = request.session['schema']

        return response
<commit_msg>Allow staff, rather than superusers, to view all schemata.<commit_after>"""
Middleware to automatically set the schema (namespace).

if request.user.is_staff, then look for a ?__schema=XXX and set the schema to that.

Otherwise, set the schema to the one associated with the logged in user.

"""
from django.core.exceptions import ObjectDoesNotExist
from django.contrib import messages
from django.utils.translation import ugettext as _

from models import Schema

def activate_schema(request, available_schemata):
    if '__schema' in request.GET:
        request.session['schema'] = request.GET['__schema']
    if 'schema' in request.session:
        try:
            available_schemata.get(pk=request.session['schema']).activate()
        except ObjectDoesNotExist:
            messages.add_message(request, messages.WARNING,
                _(u'Unable to find Schema matching query: %s' % request.session['schema'])
            )
            request.session.pop('schema')
        else:
            return None

class SchemaMiddleware:
    def process_request(self, request):
        if request.user.is_anonymous():
            return None
        if request.user.is_staff:
            available_schemata = Schema.objects
        elif request.user.schemata.exists():
            available_schemata = request.user.schemata

        activate_schema(request, available_schemata)

    def process_template_response(self, request, response):
        if request.user.is_anonymous():
            return response

        if request.user.is_staff:
            available_schemata = Schema.objects.all()
        elif request.user.schemata.exists():
            available_schemata = request.user.schemata.all()
        else:
            # No schemata available for this user?
            available_schemata = Schema.objects.none()

        response.context_data['schemata'] = available_schemata

        if 'schema' in request.session:
            response.context_data['selected_schema'] = request.session['schema']

        return response
<|endoftext|>


<commit_before>import functools
import operator
import westpa.tools.binning

from abc import ABC, abstractmethod
from westpa.core.h5io import WESTPAH5File
from westpa.core.states import BasisState, InitialState, TargetState

class Run:
    """A completed WESTPA simulation run.

    Parameters
    ----------
    h5filename : str, default 'west.h5'
        Pathname of a WESTPA HDF5 data file.
    segment_traj_loader : callable, optional
        A function to be used to load segment trajectories. The required
        signature and return type of `seg_traj_loader` are specified by the
        :class:`SegmentTrajectoryLoader` abstract base class.

    """

    def __init__(self, h5filename='west.h5', segment_traj_loader=None):
        self.h5file = WESTPAH5File(h5filename, 'r')
        self.segment_traj_loader = segment_traj_loader

    @property
    def segment_traj_loader(self):
        """callable: Function used to load segment trajectories."""
        return self._segment_traj_loader

    @segment_traj_loader.setter
    def segment_traj_loader(self, value):
        if value is not None:
            _ = value(1, 0)
        self._segment_traj_loader = value

    @property
    def summary(self):
        """h5py.Dataset: The 'summary' dataset of the HDF5 file."""
        return self.h5file['summary']

    @property
    def basis_state_info(self):
        """h5py.Dataset: 'bstate_index' dataset."""
        return self.h5group['ibstates']['bstate_index']

    @property
    def num_iterations(self):
        """int: Number of iterations in the run."""
        return len(self.h5file['iterations'])

    @property
    def iterations(self):
        """Iterable[Iteration]: Sequence of iterations."""
        return (Iteration(number, self)
                for number in range(1, self.num_iterations + 1))

    def iteration(self, number):
        """Return the iteration with the given iteration number.

        Parameters
        ----------
        number : int
            Iteration number (1-based).

        Returns
        -------
        Iteration
            The iteration indexed by `number`.

        """
        valid_range = range(1, self.num_iterations + 1)
        if number not in valid_range:
            raise ValueError(f'iteration number must be in {valid_range}')
        return Iteration(number, self)

    def __len__(self):
        return self.num_iterations

    def __iter__(self):
        return iter(self.iterations)

    def __container__(self, iteration):
        return iteration.run is self

    def __repr__(self):
        return f"{self.__class__.__name__}('{self.h5file.filename}')"

class Iteration:
    """An iteration of a WESTPA simulation.

    Parameters
    ----------
    number : int
        Iteration number (1-based).
    run : Run
        Simulation run to which the iteration belongs.

    """
    def __init__(self, number, run):
        self.number = number
        self.run = run

    @property
    def h5group(self):
        """h5py.Group: HDF5 group containing the iteration data."""
        return self.run.h5file.get_iter_group(self.number)

    @property
    def prev(self):
        """Iteration: Previous iteration."""
        if self.number == 1:
            return None
        return self.run.iteration(self.number - 1)

    @property
    def next(self):
        """Iteration: Next iteration."""
        if self.number == self.run.num_iterations:
            return None
        return self.run.iteration(self.number + 1)

    @property
    def segment_info(self):
        """h5py.Dataset: 'seg_index' dataset of the iteration."""
        return self.h5group['seg_index']

    @property
    def pcoords(self):
        """h5py.Dataset: 'pcoord' dataset of the iteration."""
        return self.h5group['pcoord']

    @property
    def bin_target_counts(self):
        """h5py.Dataset: 'bin_target_counts' dataset of the iteration."""
        return self.h5group.get('bin_target_counts')  # May be None.

    @functools.cached_property
    def bin_mapper(self):
        """BinMapper: Bin mapper used in the iteration."""
        if self.bin_target_counts is None:
            return None
        mapper, _, _ = westpa.tools.binning.mapper_from_hdf5(
            self.run.h5file['bin_topologies'],
            self.h5group.attrs['binhash'])
        return mapper

    @property
    def num_bins(self):
        """int: Number of bins."""
        return self.bin_target_counts.shape[0]

    @property
    def bins(self):
        """Iterable[Bin]: Bins."""
        return (Bin(index, self) for index in range(self.num_bins))

    @property
    def num_segments(self):
        """int: Number of segments in the iteration."""
        return self.segment_info.shape[0]

    @property
    def segment_weights(self):
        """1D ndarray: Statistical weights of the segments."""
        return self.segment_info['weight']

    @property
    def segments(self):
        """Iterable[Segment]: Segments of the iteration."""
        return (Segment(index, self) for index in range(self.num_segments))

    @property
    def _ibstates(self):
        return self.h5group['ibstates']

    @property
    def _tstates(self):
        return self.h5group['tstates']

    @property
    def basis_state_info(self):
        """h5py.Dataset: 'bstate_index' dataset."""
        return self._ibstates['bstate_index']

    @property
    def basis_state_pcoords(self):
        """h5py.Dataset. 'bstate_pcoord' dataset."""
        return self._ibstates['bstate_pcoord']

    @property
    def basis_states(self):
        """list[BasisState]: Basis states in use for the iteration."""
        return [BasisState(info['label'], info['probability'], pcoord=pcoord,
                           auxref=info['auxref'], state_id=state_id)
                for state_id, (info, pcoord) in enumerate(
                    zip(self.basis_state_info, self.basis_state_pcoords))]

    @property
    def initial_state_info(self):
        """h5py.Dataset. 'istate_index' dataset."""
        return self._ibstates['istate_index']

    @property
    def initial_state_pcoords(self):
        """h5py.Dataset. 'istate_pcoord' dataset."""
        return self._ibstates['istate_pcoord']

    @property
    def initial_states(self):
        """list[InitialState]: Initial states."""
        return [InitialState(state_id, info['basis_state_id'],
                             info['iter_created'], iter_used=info['iter_used'],
                             istate_type=info['istate_type'],
                             istate_status=info['istate_status'],
                             pcoord=pcoord)
                for state_id, (info, pcoord) in enumerate(
                    zip(self.initial_state_info, self.initial_state_pcoords))]

    @property
    def target_state_info(self):
        """h5py.Dataset: 'index' dataset for target states."""
        return self._tstates['index']

    @property
    def target_state_pcoords(self):
        """h5py.Dataset: 'pcoord' dataset for target states."""
        return self._tstates['pcoord']

    @property
    def target_states(self):
        """list[TargetState]: Target states."""
        return [TargetState(info['label'], pcoord, state_id=state_id)
                for state_id, (info, pcoord) in enumerate(
                    zip(self.target_state_info, self.target_state_pcoords))]

    @property
    def target(self):
        """Target: Union of bins that serve as the target."""
        if not self.next:
            return None
        return Target(self)

    def bin(self, index):
        """Return the bin with the given index.

        Parameters
        ----------
        index : int
            Bin index.

        Returns
        -------
        Bin
            The bin indexed by `index`.

        """
        valid_range = range(self.num_bins)
        if index not in valid_range:
            raise ValueError(f'bin index must be in {valid_range}')
        return Bin(index, self)

    def segment(self, index):
        """Return the segment with the given index.

        Parameters
        ----------
        index : int
            Segment index (0-based).

        Returns
        -------
        Segment
            The segment indexed by `index`.

        """
        valid_range = range(self.num_segments)
        if index not in valid_range:
            raise ValueError(f'segment index must be in {valid_range}')
        return Segment(index, self)

    def __iter__(self):
        return iter(self.segments)

    def __contains__(self, segment):
        return segment.iteration is self

    def __eq__(self, other):
        return self.number == other.number and self.run is other.run

    def __repr__(self):
        return f'{self.__class__.__name__}({self.number}, {self.run})'

class Segment:
    """A segment of an iteration of a WESTPA simulation.

    Parameters
    ----------
    index : int
        Segment index (0-based).
    iteration : Iteration
        Iteration to which the segment belongs.

    """

    def __init__(self, index, iteration):
        self.index = index
        self.iteration = iteration

    @property
    def run(self):
        """Run: Run to which the segment belongs."""
        return self.iteration.run

    @property
    def info(self):
        """numpy.void: 'seg_index' row of the segment."""
        return self.iteration.segment_info[self.index]

    @property
    def weight(self):
        """float64: Statistical weight of the segment."""
        return self.info['weight']

    @property
    def pcoords(self):
        """2D ndarray: Progress coordinates at each snapshot time."""
        return self.iteration.pcoords[self.index]

    @property
    def num_snapshots(self):
        """int: Number of snapshots (saved frames)."""
        return self.pcoords.shape[0]

    @property
    def parent(self):
        """Segment: The parent of the segment."""
        if not self.iteration.prev:
            return None
        return Segment(self.info['parent_id'], self.iteration.prev)

    @property
    def children(self):
        """Iterable[Segment]: The children of the segment."""
        if not self.iteration.next:
            return ()
        return (segment for segment in self.iteration.next.segments
                if segment.parent == self)

    def trace(self):
        """Return the trace (ancestral line) of the segment.

        Returns
        -------
        Trace
            The trace of the segment.

        """
        return Trace(self)

    def trajectory(self):
        """Return the trajectory of the segment.

        Returns
        -------
        sequence
            The trajectory of the segment.

        See Also
        --------
        Run.segment_traj_loader
            Function used to load the trajectory.

         """
        if not self.run.segment_traj_loader:
            raise ValueError('segment trajectory loader must be set')
        return self.run.segment_traj_loader(
            self.iteration.number, self.index)

    def __len__(self):
        return self.num_snapshots

    def __eq__(self, other):
        return self.index == other.index and self.iteration == other.iteration

    def __repr__(self):
        return f'{self.__class__.__name__}({self.index}, {self.iteration})'

class SegmentTrajectoryLoader(ABC):
    """API for segment trajectory loaders."""

    @abstractmethod
    def __call__(self, iteration_number, segment_index):
        """Return the trajectory of a given segment.

        Parameters
        ----------
        iteration_number : int
            Number (1-based) of iteration to which the segment belongs.
        segment_index : int
            Index (0-based) of the segment within the iteration.

        Returns
        -------
        sequence
            The trajectory of the segment.

        """
        ...

class Bin:
    """A bin used in the resampling step of an iteration.

    Parameters
    ----------
    index : int
        Bin index.
    iteration : Iteration
        The iteration in which the bin was used.

    """

    def __init__(self, index, iteration):
        self.index = index
        self.iteration = iteration

    @property
    def mapper(self):
        """BinMapper: Bin mapper that defines the bin."""
        return self.iteration.bin_mapper

    @property
    def target_count(self):
        """int: Target number of particles in the bin."""
        return self.iteration.bin_target_counts[self.index]

    def __contains__(self, pcoord):
        result = self.mapper.assign([pcoord])
        if result.size != 1:
            raise ValueError('left operand must be a single point in '
                             'progress coordinate space')
        return result[0] == self.index

    def __repr__(self):
        return f'{self.__class__.__name__}({self.index}, {self.iteration})'

class BinUnion:
    """A union of bins.

    Parameters
    ----------
    *bins : Bin
        The bins comprising the union.

    """

    def __init__(self, *bins):
        if not all(isinstance(bin_, Bin) for bin_ in bins):
            raise TypeError('arguments must be of type Bin')
        self._bins = bins

    @property
    def bins(self):
        """tuple[Bin]: Bins comprising the union."""
        return self._bins

    def __bool__(self):
        return bool(self.bins)

    def __contains__(self, pcoord):
        return any(pcoord in bin_ for bin_ in self.bins)

    def __repr__(self):
        return f'{self.__class__.__name__}{self.bins}'

class Target(BinUnion):
    """A union of bins that serve as the target for an iteration.

    Parameters
    ----------
    iteration : Iteration
        The iteration for which the target is defined.

    Notes
    -----
    The bins of the target belong to the *next* iteration.

    """

    def __init__(self, iteration):
        pcoords = iteration.target_state_pcoords[:]
        bin_indices = set(iteration.next.bin_mapper.assign(pcoords))
        super().__init__(*(Bin(i, iteration.next) for i in bin_indices))
        self.iteration = iteration

    @property
    def states(self):
        """list[TargetState]: Target states defining the target."""
        return self.iteration.target_states

    def __repr__(self):
        return f'{self.__class__.__name__}({self.iteration})'

class Trace:
    """A trace of a segment back to a source or initial state.

    Parameters
    ----------
    segment : Segment
        The traced segment.
    source : Bin or BinUnion, optional
        The source (macro)state. If provided, the trace is continued
        only as far back as `source`. Otherwise, the trace extends back
        to the initial state.

    """

    def __init__(self, segment, source=None):
        if source is None:
            source = BinUnion()

        segments = []
        while segment and segment.pcoords[-1] not in source:
            segments.append(segment)
            segment = segment.parent

        self.segments = tuple(reversed(segments))
        self.source = source

    def trajectory(self, concat_operator=None):
        """Return the trajectory of the trace.

        Parameters
        ----------
        concat_operator : callable, default :func:`operator.concat`
            A function that takes two trajectories and returns their
            concatenation. This parameter may be provided if the type
            returned by the segment trajectory loader does not support
            concatenation via ``traj1 + traj2`` (the default).

        Returns
        -------
        sequence
            The trajectory of the trace.

        See Also
        --------
        Run.segment_traj_loader
            Function used to load the segment trajectories.

        """
        if concat_operator is None:
            concat_operator = operator.concat
        trajectories = (segment.trajectory() for segment in self.segments)
        return functools.reduce(concat_operator, trajectories)

    def __repr__(self):
        s = f'{self.__class__.__name__}({self.segments[-1]}'
        if self.source:
            s += f', source={self.source}'
        return s + ')'
<commit_msg>Added num_segments() method to Run.<commit_after>import functools
import operator
import westpa.tools.binning

from abc import ABC, abstractmethod
from westpa.core.h5io import WESTPAH5File
from westpa.core.states import BasisState, InitialState, TargetState

class Run:
    """A completed WESTPA simulation run.

    Parameters
    ----------
    h5filename : str, default 'west.h5'
        Pathname of a WESTPA HDF5 data file.
    segment_traj_loader : callable, optional
        A function to be used to load segment trajectories. The required
        signature and return type of `seg_traj_loader` are specified by the
        :class:`SegmentTrajectoryLoader` abstract base class.

    """

    def __init__(self, h5filename='west.h5', segment_traj_loader=None):
        self.h5file = WESTPAH5File(h5filename, 'r')
        self.segment_traj_loader = segment_traj_loader

    @property
    def segment_traj_loader(self):
        """callable: Function used to load segment trajectories."""
        return self._segment_traj_loader

    @segment_traj_loader.setter
    def segment_traj_loader(self, value):
        if value is not None:
            _ = value(1, 0)
        self._segment_traj_loader = value

    @property
    def summary(self):
        """h5py.Dataset: The 'summary' dataset of the HDF5 file."""
        return self.h5file['summary']

    @property
    def basis_state_info(self):
        """h5py.Dataset: 'bstate_index' dataset."""
        return self.h5group['ibstates']['bstate_index']

    @property
    def num_iterations(self):
        """int: Number of iterations in the run."""
        return len(self.h5file['iterations'])

    @property
    def iterations(self):
        """Iterable[Iteration]: Sequence of iterations."""
        return (Iteration(number, self)
                for number in range(1, self.num_iterations + 1))

    @property
    def num_segments(self):
        """int: Total number of segments."""
        return sum(iteration.num_segments for iteration in self)

    def iteration(self, number):
        """Return the iteration with the given iteration number.

        Parameters
        ----------
        number : int
            Iteration number (1-based).

        Returns
        -------
        Iteration
            The iteration indexed by `number`.

        """
        valid_range = range(1, self.num_iterations + 1)
        if number not in valid_range:
            raise ValueError(f'iteration number must be in {valid_range}')
        return Iteration(number, self)

    def __len__(self):
        return self.num_iterations

    def __iter__(self):
        return iter(self.iterations)

    def __container__(self, iteration):
        return iteration.run is self

    def __repr__(self):
        return f"{self.__class__.__name__}('{self.h5file.filename}')"

class Iteration:
    """An iteration of a WESTPA simulation.

    Parameters
    ----------
    number : int
        Iteration number (1-based).
    run : Run
        Simulation run to which the iteration belongs.

    """
    def __init__(self, number, run):
        self.number = number
        self.run = run

    @property
    def h5group(self):
        """h5py.Group: HDF5 group containing the iteration data."""
        return self.run.h5file.get_iter_group(self.number)

    @property
    def prev(self):
        """Iteration: Previous iteration."""
        if self.number == 1:
            return None
        return self.run.iteration(self.number - 1)

    @property
    def next(self):
        """Iteration: Next iteration."""
        if self.number == self.run.num_iterations:
            return None
        return self.run.iteration(self.number + 1)

    @property
    def segment_info(self):
        """h5py.Dataset: 'seg_index' dataset of the iteration."""
        return self.h5group['seg_index']

    @property
    def pcoords(self):
        """h5py.Dataset: 'pcoord' dataset of the iteration."""
        return self.h5group['pcoord']

    @property
    def bin_target_counts(self):
        """h5py.Dataset: 'bin_target_counts' dataset of the iteration."""
        return self.h5group.get('bin_target_counts')  # May be None.

    @functools.cached_property
    def bin_mapper(self):
        """BinMapper: Bin mapper used in the iteration."""
        if self.bin_target_counts is None:
            return None
        mapper, _, _ = westpa.tools.binning.mapper_from_hdf5(
            self.run.h5file['bin_topologies'],
            self.h5group.attrs['binhash'])
        return mapper

    @property
    def num_bins(self):
        """int: Number of bins."""
        return self.bin_target_counts.shape[0]

    @property
    def bins(self):
        """Iterable[Bin]: Bins."""
        return (Bin(index, self) for index in range(self.num_bins))

    @property
    def num_segments(self):
        """int: Number of segments in the iteration."""
        return self.segment_info.shape[0]

    @property
    def segment_weights(self):
        """1D ndarray: Statistical weights of the segments."""
        return self.segment_info['weight']

    @property
    def segments(self):
        """Iterable[Segment]: Segments of the iteration."""
        return (Segment(index, self) for index in range(self.num_segments))

    @property
    def _ibstates(self):
        return self.h5group['ibstates']

    @property
    def _tstates(self):
        return self.h5group['tstates']

    @property
    def basis_state_info(self):
        """h5py.Dataset: 'bstate_index' dataset."""
        return self._ibstates['bstate_index']

    @property
    def basis_state_pcoords(self):
        """h5py.Dataset. 'bstate_pcoord' dataset."""
        return self._ibstates['bstate_pcoord']

    @property
    def basis_states(self):
        """list[BasisState]: Basis states in use for the iteration."""
        return [BasisState(info['label'], info['probability'], pcoord=pcoord,
                           auxref=info['auxref'], state_id=state_id)
                for state_id, (info, pcoord) in enumerate(
                    zip(self.basis_state_info, self.basis_state_pcoords))]

    @property
    def initial_state_info(self):
        """h5py.Dataset. 'istate_index' dataset."""
        return self._ibstates['istate_index']

    @property
    def initial_state_pcoords(self):
        """h5py.Dataset. 'istate_pcoord' dataset."""
        return self._ibstates['istate_pcoord']

    @property
    def initial_states(self):
        """list[InitialState]: Initial states."""
        return [InitialState(state_id, info['basis_state_id'],
                             info['iter_created'], iter_used=info['iter_used'],
                             istate_type=info['istate_type'],
                             istate_status=info['istate_status'],
                             pcoord=pcoord)
                for state_id, (info, pcoord) in enumerate(
                    zip(self.initial_state_info, self.initial_state_pcoords))]

    @property
    def target_state_info(self):
        """h5py.Dataset: 'index' dataset for target states."""
        return self._tstates['index']

    @property
    def target_state_pcoords(self):
        """h5py.Dataset: 'pcoord' dataset for target states."""
        return self._tstates['pcoord']

    @property
    def target_states(self):
        """list[TargetState]: Target states."""
        return [TargetState(info['label'], pcoord, state_id=state_id)
                for state_id, (info, pcoord) in enumerate(
                    zip(self.target_state_info, self.target_state_pcoords))]

    @property
    def target(self):
        """Target: Union of bins that serve as the target."""
        if not self.next:
            return None
        return Target(self)

    def bin(self, index):
        """Return the bin with the given index.

        Parameters
        ----------
        index : int
            Bin index.

        Returns
        -------
        Bin
            The bin indexed by `index`.

        """
        valid_range = range(self.num_bins)
        if index not in valid_range:
            raise ValueError(f'bin index must be in {valid_range}')
        return Bin(index, self)

    def segment(self, index):
        """Return the segment with the given index.

        Parameters
        ----------
        index : int
            Segment index (0-based).

        Returns
        -------
        Segment
            The segment indexed by `index`.

        """
        valid_range = range(self.num_segments)
        if index not in valid_range:
            raise ValueError(f'segment index must be in {valid_range}')
        return Segment(index, self)

    def __iter__(self):
        return iter(self.segments)

    def __contains__(self, segment):
        return segment.iteration is self

    def __eq__(self, other):
        return self.number == other.number and self.run is other.run

    def __repr__(self):
        return f'{self.__class__.__name__}({self.number}, {self.run})'

class Segment:
    """A segment of an iteration of a WESTPA simulation.

    Parameters
    ----------
    index : int
        Segment index (0-based).
    iteration : Iteration
        Iteration to which the segment belongs.

    """

    def __init__(self, index, iteration):
        self.index = index
        self.iteration = iteration

    @property
    def run(self):
        """Run: Run to which the segment belongs."""
        return self.iteration.run

    @property
    def info(self):
        """numpy.void: 'seg_index' row of the segment."""
        return self.iteration.segment_info[self.index]

    @property
    def weight(self):
        """float64: Statistical weight of the segment."""
        return self.info['weight']

    @property
    def pcoords(self):
        """2D ndarray: Progress coordinates at each snapshot time."""
        return self.iteration.pcoords[self.index]

    @property
    def num_snapshots(self):
        """int: Number of snapshots (saved frames)."""
        return self.pcoords.shape[0]

    @property
    def parent(self):
        """Segment: The parent of the segment."""
        if not self.iteration.prev:
            return None
        return Segment(self.info['parent_id'], self.iteration.prev)

    @property
    def children(self):
        """Iterable[Segment]: The children of the segment."""
        if not self.iteration.next:
            return ()
        return (segment for segment in self.iteration.next.segments
                if segment.parent == self)

    def trace(self):
        """Return the trace (ancestral line) of the segment.

        Returns
        -------
        Trace
            The trace of the segment.

        """
        return Trace(self)

    def trajectory(self):
        """Return the trajectory of the segment.

        Returns
        -------
        sequence
            The trajectory of the segment.

        See Also
        --------
        Run.segment_traj_loader
            Function used to load the trajectory.

         """
        if not self.run.segment_traj_loader:
            raise ValueError('segment trajectory loader must be set')
        return self.run.segment_traj_loader(
            self.iteration.number, self.index)

    def __len__(self):
        return self.num_snapshots

    def __eq__(self, other):
        return self.index == other.index and self.iteration == other.iteration

    def __repr__(self):
        return f'{self.__class__.__name__}({self.index}, {self.iteration})'

class SegmentTrajectoryLoader(ABC):
    """API for segment trajectory loaders."""

    @abstractmethod
    def __call__(self, iteration_number, segment_index):
        """Return the trajectory of a given segment.

        Parameters
        ----------
        iteration_number : int
            Number (1-based) of iteration to which the segment belongs.
        segment_index : int
            Index (0-based) of the segment within the iteration.

        Returns
        -------
        sequence
            The trajectory of the segment.

        """
        ...

class Bin:
    """A bin used in the resampling step of an iteration.

    Parameters
    ----------
    index : int
        Bin index.
    iteration : Iteration
        The iteration in which the bin was used.

    """

    def __init__(self, index, iteration):
        self.index = index
        self.iteration = iteration

    @property
    def mapper(self):
        """BinMapper: Bin mapper that defines the bin."""
        return self.iteration.bin_mapper

    @property
    def target_count(self):
        """int: Target number of particles in the bin."""
        return self.iteration.bin_target_counts[self.index]

    def __contains__(self, pcoord):
        result = self.mapper.assign([pcoord])
        if result.size != 1:
            raise ValueError('left operand must be a single point in '
                             'progress coordinate space')
        return result[0] == self.index

    def __repr__(self):
        return f'{self.__class__.__name__}({self.index}, {self.iteration})'

class BinUnion:
    """A union of bins.

    Parameters
    ----------
    *bins : Bin
        The bins comprising the union.

    """

    def __init__(self, *bins):
        if not all(isinstance(bin_, Bin) for bin_ in bins):
            raise TypeError('arguments must be of type Bin')
        self._bins = bins

    @property
    def bins(self):
        """tuple[Bin]: Bins comprising the union."""
        return self._bins

    def __bool__(self):
        return bool(self.bins)

    def __contains__(self, pcoord):
        return any(pcoord in bin_ for bin_ in self.bins)

    def __repr__(self):
        return f'{self.__class__.__name__}{self.bins}'

class Target(BinUnion):
    """A union of bins that serve as the target for an iteration.

    Parameters
    ----------
    iteration : Iteration
        The iteration for which the target is defined.

    Notes
    -----
    The bins of the target belong to the *next* iteration.

    """

    def __init__(self, iteration):
        pcoords = iteration.target_state_pcoords[:]
        bin_indices = set(iteration.next.bin_mapper.assign(pcoords))
        super().__init__(*(Bin(i, iteration.next) for i in bin_indices))
        self.iteration = iteration

    @property
    def states(self):
        """list[TargetState]: Target states defining the target."""
        return self.iteration.target_states

    def __repr__(self):
        return f'{self.__class__.__name__}({self.iteration})'

class Trace:
    """A trace of a segment back to a source or initial state.

    Parameters
    ----------
    segment : Segment
        The traced segment.
    source : Bin or BinUnion, optional
        The source (macro)state. If provided, the trace is continued
        only as far back as `source`. Otherwise, the trace extends back
        to the initial state.

    """

    def __init__(self, segment, source=None):
        if source is None:
            source = BinUnion()

        segments = []
        while segment and segment.pcoords[-1] not in source:
            segments.append(segment)
            segment = segment.parent

        self.segments = tuple(reversed(segments))
        self.source = source

    def trajectory(self, concat_operator=None):
        """Return the trajectory of the trace.

        Parameters
        ----------
        concat_operator : callable, default :func:`operator.concat`
            A function that takes two trajectories and returns their
            concatenation. This parameter may be provided if the type
            returned by the segment trajectory loader does not support
            concatenation via ``traj1 + traj2`` (the default).

        Returns
        -------
        sequence
            The trajectory of the trace.

        See Also
        --------
        Run.segment_traj_loader
            Function used to load the segment trajectories.

        """
        if concat_operator is None:
            concat_operator = operator.concat
        trajectories = (segment.trajectory() for segment in self.segments)
        return functools.reduce(concat_operator, trajectories)

    def __repr__(self):
        s = f'{self.__class__.__name__}({self.segments[-1]}'
        if self.source:
            s += f', source={self.source}'
        return s + ')'
<|endoftext|>


<commit_before>from two.ol.base import RESTLikeHandler, applyrequest, context, json
from wheelcms_axle.node import Node
from wheelcms_axle.content import type_registry, Content, ImageContent

from wheelcms_axle.spoke import FileSpoke

from wheelcms_axle.toolbar import Toolbar
from wheelcms_axle import queries

from wheelcms_axle.base import WheelHandlerMixin

from .templates import template_registry

class WheelRESTHandler(RESTLikeHandler, WheelHandlerMixin):
    pass

class MainHandler(WheelRESTHandler):
    model = dict(instance=Node, parent=Node)
    instance = None
    parent = None

    def update_context(self, request):
        super(MainHandler, self).update_context(request)

    @context
    def toplevel(self):
        """ return toplevel navigatable/visible items. Perhaps, when logged in,
            show unpublished/not in navigation?

            Return Node or Spokes?
        """
        context = self.parent
        if self.instance:
            context = self.instance

        for child in queries.toplevel_visible_children():
            ## make sure /foo/bar does not match in /football by adding the /
            if child == context or context.path.startswith(child.path + '/'):
                yield dict(active=True, node=child)
            else:
                yield dict(active=False, node=child)

    @context
    def spoke(self):
        """ return type info for the current content, if any """
        model = self.instance.content()
        if model:
            return model.spoke()
        return None

    @context
    def content(self):
        """ return the actual content for the node / spoke """
        modelinstance = self.instance.content()
        if modelinstance:
            return modelinstance
        return None

    def formclass(self, data=None, instance=None):
        """
            Find and initialize the appropriate form for the current
            instance. Not consistently used XXX
        """
        if not self.instance or not self.instance.content():
            ## there's no instance, or it's not attached to content
            return None

        typename = self.instance.content().get_name()

        typeinfo = type_registry.get(typename)
        parent = self.instance.parent()
        try:
            content = instance.content()
        except Content.DoesNotExist:
            content = None
        return typeinfo.form(parent=parent, data=data, instance=content)

    @classmethod
    def coerce(cls, i):
        """
            coerce either a parent and instance, a parent or an instance.
            If there's both a parent and an instance, the instance is relative
            to the parent, so resolving needs to be done by combing them

            We're supporting the parent/instance combo (somewhat) but don't
            really need it - <instance>/update works fine, and no instance is
            required for /create
        """
        d = dict()

        parent_path = ""
        if i.get('parent') is not None:
            parent_path = i['parent']
            if parent_path:
                parent_path = '/' + parent_path
            parent = d['parent'] = Node.get(parent_path)
            if parent is None:
                return cls.notfound()

        if i.get('instance') is not None:
            instance_path = i['instance']
            if not instance_path:  ## it can be ""
                path = parent_path
            else:
                path = parent_path + "/" + instance_path

            d['instance'] = instance = Node.get(path)

            if instance is None:
                return cls.notfound()
        return d

    def hasaccess(self):
        user = self.user()
        return user.is_active and (user.is_superuser or
                                   user.groups.filter(name="managers").exists())

    @applyrequest
    def create(self, type, attach=False, *a, **b):
        """
            Create new sub-content on a node or attach content to an
            existing node.
        """
        # import pdb; pdb.set_trace()

        if not self.hasaccess():
            return self.forbidden()

        formclass = type_registry.get(type).form

        parent = self.parent
        if parent and parent.path:
            parentpath = parent.path
        else:
            parentpath = '/'

        self.context['redirect_cancel'] = parentpath + "?info=Create+cancelled"
        ## if attach: do not accept slug
        if self.post:
            self.context['form'] = \
            self.form = formclass(data=self.request.POST,
                                  parent=parent,
                                  attach=attach,
                                  files=self.request.FILES)
            if self.form.is_valid():
                ## form validation should handle slug uniqueness (?)
                p = self.form.save(commit=False)
                if self.user().is_authenticated():
                    p.owner = self.user()
                p.save()
                if attach:
                    parent.set(p)
                else:
                    slug = self.form.cleaned_data['slug']
                    sub = parent.add(slug)
                    sub.set(p)
                return self.redirect(parent.path or '/', success="Ok")
        else:
            self.context['form'] = formclass(parent=parent, attach=attach)
        ## Get spoke model
        self.context['type'] = type

        typeinfo = type_registry.get(type)
        self.context['typeinfo'] = dict(name=typeinfo.name, title=typeinfo.title)

        self.context['attach'] = attach
        self.context['instance'] = self.parent or '/'
        self.context['breadcrumb'] = self.breadcrumb(operation="Create")
        self.context['toolbar'] = Toolbar(self.instance, status="create")
        return self.template("wheelcms_axle/create.html")

    def update(self):
        if not self.hasaccess():
            return self.forbidden()

        instance = self.instance
        parent = instance.parent()

        self.context['redirect_cancel'] = (self.instance.path or '/') + \
                                          "?info=Update+cancelled"
        self.context['toolbar'] = Toolbar(self.instance, status="edit")

        typename = instance.content().get_name()
        typeinfo = type_registry.get(typename)
        formclass =  typeinfo.form
        slug = instance.slug()

        if self.post:
            self.context['form'] = form = formclass(parent=parent,
                                                    data=self.request.POST,
                                                    instance=instance.content())

            if form.is_valid():
                form.save()
                ## handle changed slug
                slug = form.cleaned_data.get('slug', None)
                if slug and slug != self.instance.slug():
                    self.instance.rename(slug)

                return self.redirect(instance.path, success="Updated")
        else:
            self.context['form'] = formclass(parent=parent,
                             initial=dict(slug=slug), instance=instance.content())

        self.context['toolbar'].status = 'update'
        self.context['breadcrumb'] = self.breadcrumb(operation="Edit")
        return self.template("wheelcms_axle/update.html")

    @context
    def breadcrumb(self, operation=""):
        """ generate breadcrumb path. """
        base = self.instance or self.parent
        if not base:
            ## parent
            return []

        parts = base.path.split("/")
        res = []
        for i in range(len(parts)):
            subpath = "/".join(parts[:i+1])
            node = Node.get(subpath)
            content = node.content()
            ## last entry should not get path

            path = subpath or '/'
            if not operation:
                if i == len(parts) - 1:
                    path = ""

            if node.isroot():
                if content:
                    title = "Home"
                else:
                    title = "Unattached rootnode"
            else:
                title = content.title if content \
                                      else "Unattached node %s" % (subpath or '/')
            res.append((title, path))

        if operation:
            res.append((operation, ""))

        return res

    def view(self):
        """ frontpage / view """
        action = self.kw.get('action', '')

        if self.spoke() and not self.spoke().workflow().is_published():
            if not self.hasaccess():
                return self.forbidden()

        if action:
            handler = getattr(self.spoke(), action, None)
            if handler and getattr(handler, 'action', False):
                return handler(self, self.request, action)
            else:
                return self.notfound()

        self.context['toolbar'] = Toolbar(self.instance)
        ## experimental
        if self.spoke():
            tpl = self.spoke().view_template()
            ctx = template_registry.context.get((self.spoke().__class__, tpl))
            if ctx:
                self.context.update(ctx(self.instance))

        if self.spoke():
            return self.template(self.spoke().view_template())
        return self.template("wheelcms_axle/nospoke.html")

    def list(self):
        self.instance = Node.root()
        return self.view()

    def handle_list(self):
        if not self.hasaccess():
            return self.forbidden()
        self.context['toolbar'] = Toolbar(self.instance, status="list")
        self.context['breadcrumb'] = self.breadcrumb(operation="Contents")
        return self.template("wheelcms_axle/contents.html")

    def handle_contents(self):
        """ handle the /contents method. Usually this will give a listing
            of child-content, but in certain cases this makes no sense (e.g.
            childless content, or content that simply can't have children.

            This also may behave differently depending on the user's access
        """
        if self.spoke() and self.spoke().addable_children():
            return self.redirect(self.instance.path + '/list')
        return self.redirect(self.instance.path)

    def handle_popup(self):
        """ popup experiments - #524 """
        return self.template("wheelcms_axle/popup.html")

    @json
    @applyrequest
    def handle_panel(self, path, mode):
        return self.panels(path, mode)

    def panels(self, path, mode):
        """
            Generate panels for the file selection popup
            mode can be either "link" (any content) or "image" (only image
            based content)
        """
        if not self.hasaccess():
            return self.forbidden()

        ## remove optional 'action', marked by a +
        ## not sure if this is the right place to do this, or if the browser
        ## modal should have been invoked without the action in the first place
        if '+' in path:
            path = path.split("+", 1)[0].rstrip('/')

        node = Node.get(path)
        panels = []

        for i in range(3):
            content = node.content()

            if content:
                ## FileSpoke also includes ImageSpoke
                # import pdb; pdb.set_trace()

                spoke = content.spoke()
                addables = [x for x in spoke.addable_children()
                            if issubclass(x, FileSpoke)]
                instance = dict(children=[], path=node.path or '/',
                                title=content.title,
                                meta_type=content.meta_type,
                                content=content,
                                spoke=spoke,
                                addables=addables)
            else:
                addables = [x for x in type_registry.values()
                            if issubclass(x, FileSpoke)]
                instance = dict(children=[], path=node.path or '/',
                                title="Unattached node",
                                meta_type="none",
                                content=None,
                                spoke=None,
                                addables=addables)

            for child in node.children():
                selectable = False

                if mode == "link":
                    selectable = True
                elif isinstance(child.content(), ImageContent):
                    selectable = True

                selected = path == child.path or \
                           path.startswith(child.path + '/')
                instance['children'].append(
                                      dict(title=child.content().title,
                                           path=child.path,
                                           selectable=selectable,
                                           meta_type=child.content().meta_type,
                                           selected=selected))

            panels.insert(0,
                          self.render_template("wheelcms_axle/popup_list.html",
                                               instance=instance,
                                               path=path,
                                               mode=mode,
                                               selectable=(i==0)))
            if node.isroot():
                break
            node = node.parent()
        crumbs = []
        node = Node.get(path)
        while True:
            if node.isroot():
                crumbs.insert(0, dict(path=node.path, title="Home"))
                break
            crumbs.insert(0, dict(path=node.path or '/', title=node.content().title))
            node = node.parent()

        crumbtpl = self.render_template("wheelcms_axle/popup_crumbs.html", crumbs=crumbs)
        return dict(panels=panels, path=path or '/', crumbs=crumbtpl)

    @json
    @applyrequest
    def handle_fileup(self, type):
        # import pdb; pdb.set_trace()

        if not self.hasaccess():
            return self.forbidden()

        formclass = type_registry.get(type).light_form

        parent = self.instance

        ## use get() to return type-specific rendered light-form?

        if not self.post:
            return dict(form=self.render_template("wheelcms_axle/popup_upload.html",
                                                  form=formclass(parent=parent),
                                                  type=type))

        self.context['form'] = \
        self.form = formclass(data=self.request.POST,
                              parent=parent,
                              attach=False,
                              files=self.request.FILES,
                              )

        if self.form.is_valid():
            ## form validation should handle slug uniqueness (?)
            p = self.form.save(commit=False)
            if self.user().is_authenticated():
                p.owner = self.user()
            p.save()
            slug = self.form.cleaned_data['slug']
            sub = parent.add(slug)
            sub.set(p)
            return dict(status="ok", path=sub.path)

        ## for now, assume that if something went wrong, it's with the uploaded file
        return dict(status="error", errors=dict(storage=self.form.errors['storage'].pop()))

    def handle_download(self):
        """
            This doesn't work as expected. handle_download is defined on the spoke,
            it requires support in the handler in order to be accessed.

            XXX

            We need a generic way to expose additional methods on spokes
        """
        try:
            return self.spoke().handle_download()
        except AttributeError:
            return self.notfound()
<commit_msg>resolve self.spoke() just once<commit_after>from two.ol.base import RESTLikeHandler, applyrequest, context, json
from wheelcms_axle.node import Node
from wheelcms_axle.content import type_registry, Content, ImageContent

from wheelcms_axle.spoke import FileSpoke

from wheelcms_axle.toolbar import Toolbar
from wheelcms_axle import queries

from wheelcms_axle.base import WheelHandlerMixin

from .templates import template_registry

class WheelRESTHandler(RESTLikeHandler, WheelHandlerMixin):
    pass

class MainHandler(WheelRESTHandler):
    model = dict(instance=Node, parent=Node)
    instance = None
    parent = None

    def update_context(self, request):
        super(MainHandler, self).update_context(request)

    @context
    def toplevel(self):
        """ return toplevel navigatable/visible items. Perhaps, when logged in,
            show unpublished/not in navigation?

            Return Node or Spokes?
        """
        context = self.parent
        if self.instance:
            context = self.instance

        for child in queries.toplevel_visible_children():
            ## make sure /foo/bar does not match in /football by adding the /
            if child == context or context.path.startswith(child.path + '/'):
                yield dict(active=True, node=child)
            else:
                yield dict(active=False, node=child)

    @context
    def spoke(self):
        """ return type info for the current content, if any """
        model = self.instance.content()
        if model:
            return model.spoke()
        return None

    @context
    def content(self):
        """ return the actual content for the node / spoke """
        modelinstance = self.instance.content()
        if modelinstance:
            return modelinstance
        return None

    def formclass(self, data=None, instance=None):
        """
            Find and initialize the appropriate form for the current
            instance. Not consistently used XXX
        """
        if not self.instance or not self.instance.content():
            ## there's no instance, or it's not attached to content
            return None

        typename = self.instance.content().get_name()

        typeinfo = type_registry.get(typename)
        parent = self.instance.parent()
        try:
            content = instance.content()
        except Content.DoesNotExist:
            content = None
        return typeinfo.form(parent=parent, data=data, instance=content)

    @classmethod
    def coerce(cls, i):
        """
            coerce either a parent and instance, a parent or an instance.
            If there's both a parent and an instance, the instance is relative
            to the parent, so resolving needs to be done by combing them

            We're supporting the parent/instance combo (somewhat) but don't
            really need it - <instance>/update works fine, and no instance is
            required for /create
        """
        d = dict()

        parent_path = ""
        if i.get('parent') is not None:
            parent_path = i['parent']
            if parent_path:
                parent_path = '/' + parent_path
            parent = d['parent'] = Node.get(parent_path)
            if parent is None:
                return cls.notfound()

        if i.get('instance') is not None:
            instance_path = i['instance']
            if not instance_path:  ## it can be ""
                path = parent_path
            else:
                path = parent_path + "/" + instance_path

            d['instance'] = instance = Node.get(path)

            if instance is None:
                return cls.notfound()
        return d

    def hasaccess(self):
        user = self.user()
        return user.is_active and (user.is_superuser or
                                   user.groups.filter(name="managers").exists())

    @applyrequest
    def create(self, type, attach=False, *a, **b):
        """
            Create new sub-content on a node or attach content to an
            existing node.
        """
        # import pdb; pdb.set_trace()

        if not self.hasaccess():
            return self.forbidden()

        formclass = type_registry.get(type).form

        parent = self.parent
        if parent and parent.path:
            parentpath = parent.path
        else:
            parentpath = '/'

        self.context['redirect_cancel'] = parentpath + "?info=Create+cancelled"
        ## if attach: do not accept slug
        if self.post:
            self.context['form'] = \
            self.form = formclass(data=self.request.POST,
                                  parent=parent,
                                  attach=attach,
                                  files=self.request.FILES)
            if self.form.is_valid():
                ## form validation should handle slug uniqueness (?)
                p = self.form.save(commit=False)
                if self.user().is_authenticated():
                    p.owner = self.user()
                p.save()
                if attach:
                    parent.set(p)
                else:
                    slug = self.form.cleaned_data['slug']
                    sub = parent.add(slug)
                    sub.set(p)
                return self.redirect(parent.path or '/', success="Ok")
        else:
            self.context['form'] = formclass(parent=parent, attach=attach)
        ## Get spoke model
        self.context['type'] = type

        typeinfo = type_registry.get(type)
        self.context['typeinfo'] = dict(name=typeinfo.name, title=typeinfo.title)

        self.context['attach'] = attach
        self.context['instance'] = self.parent or '/'
        self.context['breadcrumb'] = self.breadcrumb(operation="Create")
        self.context['toolbar'] = Toolbar(self.instance, status="create")
        return self.template("wheelcms_axle/create.html")

    def update(self):
        if not self.hasaccess():
            return self.forbidden()

        instance = self.instance
        parent = instance.parent()

        self.context['redirect_cancel'] = (self.instance.path or '/') + \
                                          "?info=Update+cancelled"
        self.context['toolbar'] = Toolbar(self.instance, status="edit")

        typename = instance.content().get_name()
        typeinfo = type_registry.get(typename)
        formclass =  typeinfo.form
        slug = instance.slug()

        if self.post:
            self.context['form'] = form = formclass(parent=parent,
                                                    data=self.request.POST,
                                                    instance=instance.content())

            if form.is_valid():
                form.save()
                ## handle changed slug
                slug = form.cleaned_data.get('slug', None)
                if slug and slug != self.instance.slug():
                    self.instance.rename(slug)

                return self.redirect(instance.path, success="Updated")
        else:
            self.context['form'] = formclass(parent=parent,
                             initial=dict(slug=slug), instance=instance.content())

        self.context['toolbar'].status = 'update'
        self.context['breadcrumb'] = self.breadcrumb(operation="Edit")
        return self.template("wheelcms_axle/update.html")

    @context
    def breadcrumb(self, operation=""):
        """ generate breadcrumb path. """
        base = self.instance or self.parent
        if not base:
            ## parent
            return []

        parts = base.path.split("/")
        res = []
        for i in range(len(parts)):
            subpath = "/".join(parts[:i+1])
            node = Node.get(subpath)
            content = node.content()
            ## last entry should not get path

            path = subpath or '/'
            if not operation:
                if i == len(parts) - 1:
                    path = ""

            if node.isroot():
                if content:
                    title = "Home"
                else:
                    title = "Unattached rootnode"
            else:
                title = content.title if content \
                                      else "Unattached node %s" % (subpath or '/')
            res.append((title, path))

        if operation:
            res.append((operation, ""))

        return res

    def view(self):
        """ frontpage / view """
        action = self.kw.get('action', '')
        spoke = self.spoke()

        if spoke and not spoke.workflow().is_published():
            if not self.hasaccess():
                return self.forbidden()

        if action:
            handler = getattr(spoke, action, None)
            if handler and getattr(handler, 'action', False):
                return handler(self, self.request, action)
            else:
                return self.notfound()

        self.context['toolbar'] = Toolbar(self.instance)
        ## experimental
        if spoke:
            tpl = spoke.view_template()
            ctx = template_registry.context.get((spoke.__class__, tpl))
            if ctx:
                self.context.update(ctx(self.instance))

        if spoke:
            return self.template(spoke.view_template())
        return self.template("wheelcms_axle/nospoke.html")

    def list(self):
        self.instance = Node.root()
        return self.view()

    def handle_list(self):
        if not self.hasaccess():
            return self.forbidden()
        self.context['toolbar'] = Toolbar(self.instance, status="list")
        self.context['breadcrumb'] = self.breadcrumb(operation="Contents")
        return self.template("wheelcms_axle/contents.html")

    def handle_contents(self):
        """ handle the /contents method. Usually this will give a listing
            of child-content, but in certain cases this makes no sense (e.g.
            childless content, or content that simply can't have children.

            This also may behave differently depending on the user's access
        """
        if self.spoke() and self.spoke().addable_children():
            return self.redirect(self.instance.path + '/list')
        return self.redirect(self.instance.path)

    def handle_popup(self):
        """ popup experiments - #524 """
        return self.template("wheelcms_axle/popup.html")

    @json
    @applyrequest
    def handle_panel(self, path, mode):
        return self.panels(path, mode)

    def panels(self, path, mode):
        """
            Generate panels for the file selection popup
            mode can be either "link" (any content) or "image" (only image
            based content)
        """
        if not self.hasaccess():
            return self.forbidden()

        ## remove optional 'action', marked by a +
        ## not sure if this is the right place to do this, or if the browser
        ## modal should have been invoked without the action in the first place
        if '+' in path:
            path = path.split("+", 1)[0].rstrip('/')

        node = Node.get(path)
        panels = []

        for i in range(3):
            content = node.content()

            if content:
                ## FileSpoke also includes ImageSpoke
                # import pdb; pdb.set_trace()

                spoke = content.spoke()
                addables = [x for x in spoke.addable_children()
                            if issubclass(x, FileSpoke)]
                instance = dict(children=[], path=node.path or '/',
                                title=content.title,
                                meta_type=content.meta_type,
                                content=content,
                                spoke=spoke,
                                addables=addables)
            else:
                addables = [x for x in type_registry.values()
                            if issubclass(x, FileSpoke)]
                instance = dict(children=[], path=node.path or '/',
                                title="Unattached node",
                                meta_type="none",
                                content=None,
                                spoke=None,
                                addables=addables)

            for child in node.children():
                selectable = False

                if mode == "link":
                    selectable = True
                elif isinstance(child.content(), ImageContent):
                    selectable = True

                selected = path == child.path or \
                           path.startswith(child.path + '/')
                instance['children'].append(
                                      dict(title=child.content().title,
                                           path=child.path,
                                           selectable=selectable,
                                           meta_type=child.content().meta_type,
                                           selected=selected))

            panels.insert(0,
                          self.render_template("wheelcms_axle/popup_list.html",
                                               instance=instance,
                                               path=path,
                                               mode=mode,
                                               selectable=(i==0)))
            if node.isroot():
                break
            node = node.parent()
        crumbs = []
        node = Node.get(path)
        while True:
            if node.isroot():
                crumbs.insert(0, dict(path=node.path, title="Home"))
                break
            crumbs.insert(0, dict(path=node.path or '/', title=node.content().title))
            node = node.parent()

        crumbtpl = self.render_template("wheelcms_axle/popup_crumbs.html", crumbs=crumbs)
        return dict(panels=panels, path=path or '/', crumbs=crumbtpl)

    @json
    @applyrequest
    def handle_fileup(self, type):
        # import pdb; pdb.set_trace()

        if not self.hasaccess():
            return self.forbidden()

        formclass = type_registry.get(type).light_form

        parent = self.instance

        ## use get() to return type-specific rendered light-form?

        if not self.post:
            return dict(form=self.render_template("wheelcms_axle/popup_upload.html",
                                                  form=formclass(parent=parent),
                                                  type=type))

        self.context['form'] = \
        self.form = formclass(data=self.request.POST,
                              parent=parent,
                              attach=False,
                              files=self.request.FILES,
                              )

        if self.form.is_valid():
            ## form validation should handle slug uniqueness (?)
            p = self.form.save(commit=False)
            if self.user().is_authenticated():
                p.owner = self.user()
            p.save()
            slug = self.form.cleaned_data['slug']
            sub = parent.add(slug)
            sub.set(p)
            return dict(status="ok", path=sub.path)

        ## for now, assume that if something went wrong, it's with the uploaded file
        return dict(status="error", errors=dict(storage=self.form.errors['storage'].pop()))

    def handle_download(self):
        """
            This doesn't work as expected. handle_download is defined on the spoke,
            it requires support in the handler in order to be accessed.

            XXX

            We need a generic way to expose additional methods on spokes
        """
        try:
            return self.spoke().handle_download()
        except AttributeError:
            return self.notfound()
<|endoftext|>


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

from mygpo.api.basic_auth import require_valid_user, check_username
from django.contrib import auth
from django.http import HttpResponse
from datetime import datetime, timedelta
from django.views.decorators.csrf import csrf_exempt

@csrf_exempt
@require_valid_user
@check_username
def login(request, username):
    """
    authenticates the user with regular http basic auth
    """

    request.session.set_expiry(datetime.utcnow()+timedelta(days=365))
    return HttpResponse()

@csrf_exempt
@check_username
def logout(request, username):
    """
    logs out the user. does nothing if he wasn't logged in
    """

    auth.logout(request)
    return HttpResponse()
<commit_msg>require POST requests for Auth API<commit_after>#
# This file is part of my.gpodder.org.
#
# my.gpodder.org is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.
#
# my.gpodder.org is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
# License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with my.gpodder.org. If not, see <http://www.gnu.org/licenses/>.
#

from mygpo.api.basic_auth import require_valid_user, check_username
from mygpo.decorators import allowed_methods
from django.contrib import auth
from django.http import HttpResponse
from datetime import datetime, timedelta
from django.views.decorators.csrf import csrf_exempt

@csrf_exempt
@require_valid_user
@check_username
@allowed_methods(['POST'])
def login(request, username):
    """
    authenticates the user with regular http basic auth
    """

    request.session.set_expiry(datetime.utcnow()+timedelta(days=365))
    return HttpResponse()

@csrf_exempt
@check_username
@allowed_methods(['POST'])
def logout(request, username):
    """
    logs out the user. does nothing if he wasn't logged in
    """

    auth.logout(request)
    return HttpResponse()
<|endoftext|>


<commit_before>#  This file is part of the myhdl library, a Python package for using
#  Python as a Hardware Description Language.
#
#  Copyright (C) 2003-2008 Jan Decaluwe
#
#  The myhdl library is free software; you can redistribute it and/or
#  modify it under the terms of the GNU Lesser General Public License as
#  published by the Free Software Foundation; either version 2.1 of the
#  License, or (at your option) any later version.
#
#  This library is distributed in the hope that it will be useful, but
#  WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#  Lesser General Public License for more details.
#
#  You should have received a copy of the GNU Lesser General Public
#  License along with this library; if not, write to the Free Software
#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

""" myhdl _extractHierarchy module.

"""

import sys
import inspect
from inspect import currentframe, getframeinfo, getouterframes
import re
import string
from types import GeneratorType
import linecache

from myhdl import ExtractHierarchyError, ToVerilogError, ToVHDLError
from myhdl._Signal import _Signal, _isListOfSigs
from myhdl._util import _isGenFunc
from myhdl._misc import _isGenSeq

_profileFunc = None

class _error:
    pass
_error.NoInstances = "No instances found"
_error.InconsistentHierarchy = "Inconsistent hierarchy - are all instances returned ?"
_error.InconsistentToplevel = "Inconsistent top level %s for %s - should be 1"

class _Instance(object):
    __slots__ = ['level', 'obj', 'subs', 'sigdict', 'memdict', 'name']
    def __init__(self, level, obj, subs, sigdict, memdict):
        self.level = level
        self.obj = obj
        self.subs = subs
        self.sigdict = sigdict
        self.memdict = memdict

_memInfoMap = {}

class _MemInfo(object):
    __slots__ = ['mem', 'name', 'elObj', 'depth', '_used', '_driven']
    def __init__(self, mem):
        self.mem = mem
        self.name = None
        self.depth = len(mem)
        self.elObj = mem[0]
        self._used = False
        self._driven = None

def _getMemInfo(mem):
    return _memInfoMap[id(mem)]

def _makeMemInfo(mem):
    key = id(mem)
    if key not in _memInfoMap:
        _memInfoMap[key] = _MemInfo(mem)
    return _memInfoMap[key]

def _isMem(mem):
    return id(mem) in _memInfoMap

_userCodeMap = {'verilog' : {},
                'vhdl' : {}
               }

class _UserCode(object):
    __slots__ = ['code', 'namespace', 'sourcefile', 'funcname', 'sourceline']
    def __init__(self, code, namespace, sourcefile, funcname, sourceline):
        self.code = code
        self.namespace = namespace
        self.sourcefile = sourcefile
        self.funcname = funcname
        self.sourceline = sourceline

    def __str__(self):
        try:
            code = self.code % self.namespace
        except:
            type, value, tb = sys.exc_info()
            info = "in file %s, function %s starting on line %s:\n    " % \
                   (self.sourcefile, self.funcname, self.sourceline)
            msg = "%s: %s" % (type, value)
            self.raiseError(msg, info)
        code = "\n%s\n" % code
        return code

class _UserVerilog(_UserCode):
    def raiseError(self, msg, info):
        raise ToVerilogError("Error in user defined Verilog code", msg, info)

class _UserVhdl(_UserCode):
    def raiseError(self, msg, info):
        raise ToVHDLError("Error in user defined VHDL code", msg, info)

def _addUserCode(hdl, arg, code, namespace, sourcefile, funcname, sourceline):
    classMap = {'verilog' : _UserVerilog,
                'vhdl' :_UserVhdl
               }
    assert id(arg) not in _userCodeMap[hdl]
    _userCodeMap[hdl][id(arg)] = classMap[hdl](code, namespace, sourcefile, funcname, sourceline)

class _CallFuncVisitor(object):

    def __init__(self):
        self.linemap = {}

    def visitAssign(self, node):
        if isinstance(node.expr, ast.CallFunc):
            self.lineno = None
            self.visit(node.expr)
            self.linemap[self.lineno] = node.lineno

    def visitName(self, node):
        self.lineno = node.lineno

class _HierExtr(object):

    def __init__(self, name, dut, *args, **kwargs):

        global _profileFunc
        _memInfoMap.clear()
        for hdl in _userCodeMap:
            _userCodeMap[hdl].clear()
        self.skipNames = ('always_comb', 'always', '_always_decorator', 'instance', \
                          'instances', 'processes', 'posedge', 'negedge')
        self.skip = 0
        self.hierarchy = hierarchy = []
        self.absnames = absnames = {}
        self.level = 0

        _profileFunc = self.extractor
        sys.setprofile(_profileFunc)
        _top = dut(*args, **kwargs)
        sys.setprofile(None)
        if not hierarchy:
            raise ExtractHierarchyError(_error.NoInstances)

        self.top = _top

        # streamline hierarchy
        hierarchy.reverse()
        # walk the hierarchy to define relative and absolute names
        names = {}
        top_inst = hierarchy[0]
        obj, subs = top_inst.obj, top_inst.subs
        names[id(obj)] = name
        absnames[id(obj)] = name
        if not top_inst.level == 1:
            raise ExtractHierarchyError(_error.InconsistentToplevel % (top_inst.level, name))
        for inst in hierarchy:
            obj, subs = inst.obj, inst.subs
            if id(obj) not in names:
                raise ExtractHierarchyError(_error.InconsistentHierarchy)
            inst.name = names[id(obj)]
            tn = absnames[id(obj)]
            for sn, so in subs:
                names[id(so)] = sn
                absnames[id(so)] = "%s_%s" % (tn, sn)
                if isinstance(so, (tuple, list)):
                    for i, soi in enumerate(so):
                        sni =  "%s_%s" % (sn, i)
                        names[id(soi)] = sni
                        absnames[id(soi)] = "%s_%s_%s" % (tn, sn, i)

    def extractor(self, frame, event, arg):

        if event == "call":

            func_name = frame.f_code.co_name
            if func_name in self.skipNames:
                self.skip = 1
            if not self.skip:
                self.level += 1

        elif event == "return":

            if not self.skip:
                isGenSeq = _isGenSeq(arg)
                if isGenSeq:
                    for hdl in _userCodeMap:
                        key = "__%s__" % hdl
                        if key in frame.f_locals:
                            code = frame.f_locals[key]
                            namespace = frame.f_globals.copy()
                            namespace.update(frame.f_locals)
                            sourcefile = inspect.getsourcefile(frame)
                            funcname = frame.f_code.co_name
                            sourceline = inspect.getsourcelines(frame)[1]
                            _addUserCode(hdl, arg, code, namespace, sourcefile, funcname, sourceline)
                # building hierarchy only makes sense if there are generators
                if isGenSeq and arg:
                    sigdict = {}
                    memdict = {}
                    cellvars = frame.f_code.co_cellvars
                    for dict in (frame.f_globals, frame.f_locals):
                        for n, v in dict.items():
                            # extract signals and memories
                            # also keep track of whether they are used in generators
                            # only include objects that are used in generators
##                             if not n in cellvars:
##                                 continue
                            if isinstance(v, _Signal):
                                sigdict[n] = v
                                if n in cellvars:
                                    v._markUsed()
                            if _isListOfSigs(v):
                                m = _makeMemInfo(v)
                                memdict[n] = m
                                if n in cellvars:
                                    m._used = True

                    subs = []
                    for n, sub in frame.f_locals.items():
                        for elt in _inferArgs(arg):
                            if elt is sub:
                                subs.append((n, sub))

                    inst = _Instance(self.level, arg, subs, sigdict, memdict)
                    self.hierarchy.append(inst)

                self.level -= 1

            func_name = frame.f_code.co_name
            if func_name in self.skipNames:
                self.skip = 0

def _inferArgs(arg):
    c = [arg]
    if isinstance(arg, (tuple, list)):
        c += list(arg)
    return c

<commit_msg>prepare _extractHierarchy for further patches<commit_after>#  This file is part of the myhdl library, a Python package for using
#  Python as a Hardware Description Language.
#
#  Copyright (C) 2003-2008 Jan Decaluwe
#
#  The myhdl library is free software; you can redistribute it and/or
#  modify it under the terms of the GNU Lesser General Public License as
#  published by the Free Software Foundation; either version 2.1 of the
#  License, or (at your option) any later version.
#
#  This library is distributed in the hope that it will be useful, but
#  WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#  Lesser General Public License for more details.
#
#  You should have received a copy of the GNU Lesser General Public
#  License along with this library; if not, write to the Free Software
#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

""" myhdl _extractHierarchy module.

"""

import sys
import inspect
from inspect import currentframe, getframeinfo, getouterframes
import re
import string
from types import GeneratorType
import linecache

from myhdl import ExtractHierarchyError, ToVerilogError, ToVHDLError
from myhdl._Signal import _Signal, _isListOfSigs
from myhdl._util import _isGenFunc
from myhdl._misc import _isGenSeq

_profileFunc = None

class _error:
    pass
_error.NoInstances = "No instances found"
_error.InconsistentHierarchy = "Inconsistent hierarchy - are all instances returned ?"
_error.InconsistentToplevel = "Inconsistent top level %s for %s - should be 1"

class _Instance(object):
    __slots__ = ['level', 'obj', 'subs', 'sigdict', 'memdict', 'name']
    def __init__(self, level, obj, subs, sigdict, memdict):
        self.level = level
        self.obj = obj
        self.subs = subs
        self.sigdict = sigdict
        self.memdict = memdict

_memInfoMap = {}

class _MemInfo(object):
    __slots__ = ['mem', 'name', 'elObj', 'depth', '_used', '_driven']
    def __init__(self, mem):
        self.mem = mem
        self.name = None
        self.depth = len(mem)
        self.elObj = mem[0]
        self._used = False
        self._driven = None

def _getMemInfo(mem):
    return _memInfoMap[id(mem)]

def _makeMemInfo(mem):
    key = id(mem)
    if key not in _memInfoMap:
        _memInfoMap[key] = _MemInfo(mem)
    return _memInfoMap[key]

def _isMem(mem):
    return id(mem) in _memInfoMap

_userCodeMap = {'verilog' : {},
                'vhdl' : {}
               }

class _UserCode(object):
    __slots__ = ['code', 'namespace', 'sourcefile', 'funcname', 'sourceline']
    def __init__(self, code, namespace, sourcefile, funcname, sourceline):
        self.code = code
        self.namespace = namespace
        self.sourcefile = sourcefile
        self.funcname = funcname
        self.sourceline = sourceline

    def __str__(self):
        try:
            code = self.code % self.namespace
        except:
            type, value, tb = sys.exc_info()
            info = "in file %s, function %s starting on line %s:\n    " % \
                   (self.sourcefile, self.funcname, self.sourceline)
            msg = "%s: %s" % (type, value)
            self.raiseError(msg, info)
        code = "\n%s\n" % code
        return code

class _UserVerilog(_UserCode):
    def raiseError(self, msg, info):
        raise ToVerilogError("Error in user defined Verilog code", msg, info)

class _UserVhdl(_UserCode):
    def raiseError(self, msg, info):
        raise ToVHDLError("Error in user defined VHDL code", msg, info)

def _addUserCode(hdl, arg, code, namespace, sourcefile, funcname, sourceline):
    classMap = {'verilog' : _UserVerilog,
                'vhdl' :_UserVhdl
               }
    assert id(arg) not in _userCodeMap[hdl]
    _userCodeMap[hdl][id(arg)] = classMap[hdl](code, namespace, sourcefile, funcname, sourceline)

class _CallFuncVisitor(object):

    def __init__(self):
        self.linemap = {}

    def visitAssign(self, node):
        if isinstance(node.expr, ast.CallFunc):
            self.lineno = None
            self.visit(node.expr)
            self.linemap[self.lineno] = node.lineno

    def visitName(self, node):
        self.lineno = node.lineno

class _HierExtr(object):

    def __init__(self, name, dut, *args, **kwargs):

        global _profileFunc
        _memInfoMap.clear()
        for hdl in _userCodeMap:
            _userCodeMap[hdl].clear()
        self.skipNames = ('always_comb', 'always', '_always_decorator', 'instance', \
                          'instances', 'processes', 'posedge', 'negedge')
        self.skip = 0
        self.hierarchy = hierarchy = []
        self.absnames = absnames = {}
        self.level = 0

        _profileFunc = self.extractor
        sys.setprofile(_profileFunc)
        _top = dut(*args, **kwargs)
        sys.setprofile(None)
        if not hierarchy:
            raise ExtractHierarchyError(_error.NoInstances)

        self.top = _top

        # streamline hierarchy
        hierarchy.reverse()
        # walk the hierarchy to define relative and absolute names
        names = {}
        top_inst = hierarchy[0]
        obj, subs = top_inst.obj, top_inst.subs
        names[id(obj)] = name
        absnames[id(obj)] = name
        if not top_inst.level == 1:
            raise ExtractHierarchyError(_error.InconsistentToplevel % (top_inst.level, name))
        for inst in hierarchy:
            obj, subs = inst.obj, inst.subs
            if id(obj) not in names:
                raise ExtractHierarchyError(_error.InconsistentHierarchy)
            inst.name = names[id(obj)]
            tn = absnames[id(obj)]
            for sn, so in subs:
                names[id(so)] = sn
                absnames[id(so)] = "%s_%s" % (tn, sn)
                if isinstance(so, (tuple, list)):
                    for i, soi in enumerate(so):
                        sni =  "%s_%s" % (sn, i)
                        names[id(soi)] = sni
                        absnames[id(soi)] = "%s_%s_%s" % (tn, sn, i)

    def extractor(self, frame, event, arg):

        if event == "call":

            funcname = frame.f_code.co_name
            # skip certain functions
            if funcname in self.skipNames:
                self.skip +=1
            if not self.skip:
                self.level += 1

        elif event == "return":

            funcname = frame.f_code.co_name
            func = frame.f_globals.get(funcname)

            if not self.skip:
                isGenSeq = _isGenSeq(arg)
                if isGenSeq:
                    for hdl in _userCodeMap:
                        key = "__%s__" % hdl
                        if key in frame.f_locals:
                            code = frame.f_locals[key]
                            namespace = frame.f_globals.copy()
                            namespace.update(frame.f_locals)
                            sourcefile = inspect.getsourcefile(frame)
                            sourceline = inspect.getsourcelines(frame)[1]
                            _addUserCode(hdl, arg, code, namespace, sourcefile, funcname, sourceline)
                # building hierarchy only makes sense if there are generators
                if isGenSeq and arg:
                    sigdict = {}
                    memdict = {}
                    cellvars = frame.f_code.co_cellvars
                    for dict in (frame.f_globals, frame.f_locals):
                        for n, v in dict.items():
                            # extract signals and memories
                            # also keep track of whether they are used in generators
                            # only include objects that are used in generators
##                             if not n in cellvars:
##                                 continue
                            if isinstance(v, _Signal):
                                sigdict[n] = v
                                if n in cellvars:
                                    v._markUsed()
                            if _isListOfSigs(v):
                                m = _makeMemInfo(v)
                                memdict[n] = m
                                if n in cellvars:
                                    m._used = True

                    subs = []
                    for n, sub in frame.f_locals.items():
                        for elt in _inferArgs(arg):
                            if elt is sub:
                                subs.append((n, sub))

                    inst = _Instance(self.level, arg, subs, sigdict, memdict)
                    self.hierarchy.append(inst)

                self.level -= 1

            if funcname in self.skipNames:
                self.skip -= 1

def _inferArgs(arg):
    c = [arg]
    if isinstance(arg, (tuple, list)):
        c += list(arg)
    return c

<|endoftext|>


<commit_before>from django.db import models
from django.conf import settings
from django.template import Template, Context

from django.contrib.sites.models import Site
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic

class SocialNetwork(models.Model):
    name = models.CharField(max_length=50, unique=True)
    site_url = models.URLField(verify_exists=False)
    icon = models.ImageField(
        upload_to="whereabouts/sn_icons/",
        max_length=200,
        null=True,
        blank=True
    )
    profile_template = models.CharField(
        max_length=255,
        blank=True,
        help_text= """
            Enter URL template with {{ profile_id }} as a placeholder for
            the user's unique id on the network. Leave blank for default
            of http://site_url/{{ profile_id }}
        """
    )

    class Meta:
        ordering = ("name",)

    def __unicode__(self):
        return self.name

    def get_absolute_url(self):
        return self.site_url

    def save(self, *args, **kwargs):
        if not self.profile_template:
            self.profile_template = "%s{{ profile_id }}" % self.site_url
        super(SocialNetwork, self).save(*args, **kwargs)

    def get_icon_image_source(self):
        return '' % (settings.MEDIA_URL, self.icon,)
    get_icon_image_source.allow_tags = True

class SocialNetworkProfile(models.Model):
    network = models.ForeignKey(SocialNetwork)
    content_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField()
    content_object = generic.GenericForeignKey("content_type", "object_id")
    profile_id = models.CharField(max_length=100)

    class Meta:
        ordering = ("network",)

    def __unicode__(self):
        return "%s's (%s) profile on %s" % (self.content_object,
                                            self.content_type.name,
                                            self.network.name)

    def get_absolute_url(self):
        t = Template(self.network.profile_template)
        c = Context({"profile_id": self.profile_id})
        return t.render(c)

class SocialNetworkWidget(models.Model):
    network = models.ForeignKey(SocialNetwork)
    name = models.CharField(max_length=50)
    slug = models.SlugField()
    description = models.TextField()
    widget_template = models.TextField()
    api_key_setting = models.CharField(
        max_length=50,
        blank=True
    )

    def __unicode__(self):
        return "%s widget" % self.name<commit_msg>Changed profile ordering to be done by pk<commit_after>from django.db import models
from django.conf import settings
from django.template import Template, Context

from django.contrib.sites.models import Site
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic

class SocialNetwork(models.Model):
    name = models.CharField(max_length=50, unique=True)
    site_url = models.URLField(verify_exists=False)
    icon = models.ImageField(
        upload_to="whereabouts/sn_icons/",
        max_length=200,
        null=True,
        blank=True
    )
    profile_template = models.CharField(
        max_length=255,
        blank=True,
        help_text= """
            Enter URL template with {{ profile_id }} as a placeholder for
            the user's unique id on the network. Leave blank for default
            of http://site_url/{{ profile_id }}
        """
    )

    class Meta:
        ordering = ("name",)

    def __unicode__(self):
        return self.name

    def get_absolute_url(self):
        return self.site_url

    def save(self, *args, **kwargs):
        if not self.profile_template:
            self.profile_template = "%s{{ profile_id }}" % self.site_url
        super(SocialNetwork, self).save(*args, **kwargs)

    def get_icon_image_source(self):
        return '' % (settings.MEDIA_URL, self.icon,)
    get_icon_image_source.allow_tags = True

class SocialNetworkProfile(models.Model):
    network = models.ForeignKey(SocialNetwork)
    content_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField()
    content_object = generic.GenericForeignKey("content_type", "object_id")
    profile_id = models.CharField(max_length=100)

    class Meta:
        ordering = ("pk",)

    def __unicode__(self):
        return "%s's (%s) profile on %s" % (self.content_object,
                                            self.content_type.name,
                                            self.network.name)

    def get_absolute_url(self):
        t = Template(self.network.profile_template)
        c = Context({"profile_id": self.profile_id})
        return t.render(c)

class SocialNetworkWidget(models.Model):
    network = models.ForeignKey(SocialNetwork)
    name = models.CharField(max_length=50)
    slug = models.SlugField()
    description = models.TextField()
    widget_template = models.TextField()
    api_key_setting = models.CharField(
        max_length=50,
        blank=True
    )

    def __unicode__(self):
        return "%s widget" % self.name<|endoftext|>