class AddProjectForm(ModelForm):
    requirements = forms.FileField(required=False)
    parser = forms.ChoiceField(choices=get_parser_choices())

    class Meta:
        model = Project
        fields = ('name', 'slug')

    def __init__(self, *args, **kwargs):
        self.user = kwargs.pop('user')
        super(AddProjectForm, self).__init__(*args, **kwargs)

    def clean(self):
        cleaned_data = self.cleaned_data
        data = cleaned_data.get('requirements', None)
        project_deps = []
        if data and 'parser' in cleaned_data:
            parser = get_parser(cleaned_data['parser'])
            packages, missing = parser.parse(data)
            packages = dict((normalize_name(k), v) for k, v
                                                   in packages.iteritems())

            pkg_names = [normalize_name(name) for name in packages.keys()]

            known_packages = Package.objects \
                .filter(normalized_name__in=pkg_names) \
                .values_list('normalized_name', 'name', 'pk')
            known_package_names = map(lambda x: x[1], known_packages)

            # TODO: report missing back to the ui.
            normalized = (normalize_name(n) for n in known_package_names)
            missing.extend(set(pkg_names).difference(normalized))

            for normalized, name, pk in known_packages:
                project_deps.append(
                    ProjectDependency(package_id=pk,
                                      version=packages[normalized]))
        cleaned_data['requirements'] = project_deps
        return cleaned_data

    def save(self, commit=True):
        project = super(AddProjectForm, self).save(True)
        project.create_logentry('project', 'add', self.user, name=project.name)
        ProjectMember.objects.create(user=self.user, state=ProjectMember.OWNER,
            project=project)
        deps = self.cleaned_data['requirements']
        for dep in deps:
            dep.project = project
        ProjectDependency.objects.bulk_create(deps)
        sync_project.delay(project.pk)
        return project class UpdateUserProfileForm(ModelForm):
    timezone = forms.ChoiceField(label=ugettext_lazy('Timezone'),
                                 required=True,
                                 choices=zip(TIMEZONES, TIMEZONES))
    jabber = JabberField(required=False)
    email = forms.EmailField(required=True)

    class Meta:
        model = UserProfile
        fields = ('email', 'jabber', 'language', 'timezone')

    def __init__(self, *args, **kwargs):
        instance = kwargs.get('instance', None)
        if instance is not None:
            initial = kwargs.setdefault('initial', {})
            initial['email'] = instance.user.email

        super(UpdateUserProfileForm, self).__init__(*args, **kwargs) class ProjectDependencyForm(ModelForm):
    class Meta:
        model = ProjectDependency
        fields = ('version', 'id') class ProjectMemberForm(ModelForm):
    class Meta:
        model = ProjectMember
        fields = ('id', 'state') class CreateProjectMemberForm(ModelForm):
    class Meta:
        model = ProjectMember
        fields = ('user', 'state') class UpdateProjectDependencyForm(forms.Form):
    packages = forms.CharField(widget=forms.Textarea, required=False)
    parser = forms.ChoiceField(choices=get_parser_choices())

    def clean(self):
        cleaned_data = self.cleaned_data
        if 'packages' in cleaned_data and 'parser' in cleaned_data:
            data = cleaned_data['packages']
            parser = get_parser(cleaned_data['parser'])
            packages, missing_packages = parser.parse(data.splitlines())
            pkg_names = [normalize_name(name) for name in packages.keys()]
            known_packages = set(
                Package.objects.filter(normalized_name__in=pkg_names)
                               .values_list('normalized_name', 'name'))
            pkg_mapping = dict(known_packages)

            # Show real package names instead of normalized version.
            unknown_packages = set(name for name in
                set(pkg_names).difference(x[0] for x in known_packages))

            if unknown_packages:
                raise ValidationError(_(
                    'Could not find the following dependencies: %s') %
                    ', '.join(unknown_packages))
            if missing_packages:
                raise ValidationError(_(
                    'Could not parse the following dependencies: %s') %
                    ', '.join(missing_packages))
            cleaned_data['packages'] = packages
        return cleaned_data class Project(models.Model):
    name = models.CharField(_('name'), max_length=255)
    slug = models.SlugField(_('slug'), unique=True)
    members = models.ManyToManyField(User, through=ProjectMember,
          verbose_name=_('members'))

    class Meta:
        verbose_name = _('project')
        verbose_name_plural = _('projects')

    @models.permalink
    def get_absolute_url(self):
        return 'folivora_project_detail', (), {'slug': self.slug}

    def create_logentry(self, type, action, user=None, **kwargs):
        Log.objects.create(project=self, type=type,
                           action=action, data=kwargs, user=user)

    @property
    def requirements(self):
        query = ProjectDependency.objects.filter(project=self) \
                                         .select_related('package') \
                                         .order_by('package__name')
        return "\n".join([d.dependency_string for d in query])

    @property
    def requirement_dict(self):
        query = ProjectDependency.objects.filter(project=self) \
                                         .select_related('package')
        return dict((d.package.name, d.version) for d in query)

    def process_changes(self, user, remove=None, change=None, add=None):
        log_entries = []

        remove = remove if remove else []
        change = change if change else []
        add = add if add else []

        for package_id, version in add:
            log_entries.append(Log(type='project_dependency', action='add',
                                   project_id=self.pk, package_id=package_id,
                                   user=user, data={'version': version}))
        for package_id, version in remove:
            log_entries.append(Log(type='project_dependency', action='remove',
                                   project_id=self.pk, package_id=package_id,
                                   user=user, data={'version': version}))
        for package_id, old_version, new_version in change:
            log_entries.append(Log(type='project_dependency', action='update',
                                   project_id=self.pk, package_id=package_id,
                                   user=user, data={'version': new_version,
                                         'old_version': old_version}))

        Log.objects.bulk_create(log_entries)
        from .tasks import sync_project
        # give the request time to finish before syncing
        sync_project.apply_async(args=[self.pk], countdown=1)

    @property
    def owners(self):
        return self.members.filter(projectmember__state=ProjectMember.OWNER) class UserProfile(models.Model):
    user = models.OneToOneField(User)
    language = models.CharField(_('Language'), max_length=255,
                                choices=settings.LANGUAGES, blank=True)
    timezone = models.CharField(_('Timezone'), max_length=255, default='UTC')
    jabber = models.CharField(_('JID'), max_length=255, blank=True)

    def get_absolute_url(self):
        return reverse('folivora_profile_edit') class ProjectDependency(models.Model):
    project = models.ForeignKey(Project, verbose_name=_('project'),
                                related_name='dependencies')
    package = models.ForeignKey(Package, verbose_name=_('package'))
    version = models.CharField(_('version'), max_length=255)
    update = models.ForeignKey(PackageVersion, verbose_name=_('update'),
                               null=True, blank=True, default=None)

    class Meta:
        verbose_name = _('project dependency')
        verbose_name_plural = _('project dependencies')
        unique_together = ('project', 'package')

    @property
    def dependency_string(self):
        return u"%s==%s" % (self.package.name, self.version)

    @property
    def update_available(self):
        return self.update_id is not None

    @classmethod
    def process_formset(cls, formset, original_data, user):
        remove = []
        change = []
        for instance in formset.deleted_objects:
            remove.append((instance.package.id, instance.version))
        for instance, d in formset.changed_objects:
            existing = original_data[instance.pk]
            change.append((instance.package.id,
                           existing.version,
                           instance.version))
        formset.instance.process_changes(user, remove, change) class ProjectMember(models.Model):
    OWNER = 0
    MEMBER = 1
    STATE_CHOICES = (
        (OWNER, _('Owner')),
        (MEMBER, _('Member'))
    )

    project = models.ForeignKey('Project', verbose_name=_('project'))
    user = models.ForeignKey(User, verbose_name=_('user'))
    state = models.IntegerField(_('state'), choices=STATE_CHOICES)
    mail = models.EmailField(_('Email'), max_length=255, blank=True)
    jabber = models.CharField(_('Jabber'), max_length=255, blank=True)

    class Meta:
        verbose_name = _('project member')
        verbose_name_plural = _('project members')
        unique_together = ('project', 'user') class Log(models.Model):
    project = models.ForeignKey(Project, verbose_name=_('project'))
    package = models.ForeignKey(Package, verbose_name=_('package'),
                                null=True, blank=True, default=None)
    user = models.ForeignKey(User, verbose_name=_('user'), null=True)
    when = models.DateTimeField(_('when'), default=now)
    action = models.CharField(_('action'), max_length=255)
    type = models.CharField(_('type'), max_length=255)
    data = hstore.DictionaryField()

    objects = hstore.HStoreManager()

    class Meta:
        verbose_name = _('log')
        verbose_name_plural = _('logs')

    def __unicode__(self):
        return '{}.{}, package: {}, project: {}'.format(self.type, self.action,
                                                        self.package_id,
                                                        self.project_id)

    @property
    def template(self):
        return 'folivora/notifications/{}.{}.html'.format(self.type,
                                                          self.action) class Package(models.Model):
    PYPI = 'pypi'
    PROVIDER_CHOICES = (
        (PYPI, 'PyPi'),
    )

    name = models.CharField(_('name'), max_length=255, unique=True)
    normalized_name = models.CharField(_('Normalized Name'), max_length=255,
                                       unique=True)
    url = models.URLField(_('url'))
    provider = models.CharField(_('provider'), max_length=255,
        choices=PROVIDER_CHOICES, default=PYPI)
    initial_sync_done = models.BooleanField(default=False)

    @classmethod
    def create_with_provider_url(cls, name, provider='pypi', url=None):
        if url is None:
            url = urlparse.urljoin(DEFAULT_SERVER, name)
        pkg = cls(name=name, url=url, provider=provider)
        pkg.save()
        return pkg

    def sync_versions(self):
        if self.initial_sync_done:
            return
        client = CheeseShop()
        versions = client.get_package_versions(self.name)
        for version in versions:
            urls = client.get_release_urls(self.name, version)
            if urls:
                url = urls[0]
                utime = time.mktime(url['upload_time'].timetuple())
                release_date = make_aware(
                    datetime.datetime.fromtimestamp(utime),
                    pytz.UTC)
                PackageVersion.objects.get_or_create(
                    package=self,
                    version=version,
                    release_date=release_date)
        self.initial_sync_done = True
        self.save()

    def save(self, *args, **kwargs):
        if not self.normalized_name:
            self.normalized_name = normalize_name(self.name)
        super(Package, self).save(*args, **kwargs)

    class Meta:
        verbose_name = _('package')
        verbose_name_plural = _('packages')
        unique_together = ('name', 'provider')

    def __unicode__(self):
        return self.name

    def __repr__(self):
        return '<%s: %s>' % (self.__class__.__name__, self.name) class SortListMixin(object):
    """This mixin allows the user to sort the queryset by predefined fields.

    This mixin is used by assigning an iterable to `sort_fields` containing
    strings which have to match the field names of the model from the
    underlying generic view.

    The default order is specified by `default_order` which has to be a string
    with the same syntax requirements as Django's `QuerySet.order_by()` method.

    Incoming requests are checked for a `sort`-GET param and verified against
    `sort_fields` before the sorting is applied.
    """
    sort_fields = None
    default_order = ('pk',)

    def get_context_data(self, **kwargs):
        context = super(SortListMixin, self).get_context_data(**kwargs)
        context.update({
            'sort_fields': self.sort_fields,
            'sort_field': self.sort_field,
            'sort_order': self.sort_order
        })
        return context

    def get_queryset(self):
        qs = super(SortListMixin, self).get_queryset()
        self.sort_field = None
        if self.sort_fields:
            sort = self.request.GET.get('sort')
            if sort and sort.strip('-') in self.sort_fields:
                self.sort_field = sort
                qs = qs.order_by(sort)
            else:
                qs = qs.order_by(*self.default_order)
                self.sort_field = self.default_order[0]
        self.sort_order = 'desc' if self.sort_field[0] == '-' else 'asc'
        self.sort_field = self.sort_field.strip('-')
        return qs class ProjectMixin(object):
    """Mixin which checks if the user has access to the project in question.

    It is written for this project and won't work anywhere else ;)

    Access is granted by checking if the user is a member of a project, the
    project is taken from the slug in the URL.

    Additionally it checks if the user is authenticated.

    Set allow_only_owner True, to grant access only to owners

    Also polulates self.project and context['project']
    """
    allow_only_owner = False

    @method_decorator(login_required)
    def dispatch(self, request, *args, **kwargs):
        slug = kwargs['slug']
        self.project = Project.objects.get(slug=slug)
        query = ProjectMember.objects.filter(
            user=request.user, project=self.project)
        if self.allow_only_owner:
            query = query.filter(state=ProjectMember.OWNER)
        if not query.exists():
            return HttpResponseForbidden('403 - Forbidden')
        return super(ProjectMixin, self).dispatch(request, *args, **kwargs)

    def get_context_data(self, **kwargs):
        context = super(ProjectMixin, self).get_context_data(**kwargs)
        context['project'] = self.project
        return context

    def get_object(self, queryset=None):
        if self.model != Project or queryset is not None:
            return super(ProjectMixin, self).get_object(queryset)
        return self.project
from django.core.urlresolvers import reverse_lazy, reverse
from django.db import models
from django.forms.models import inlineformset_factory
from django.http import HttpResponseRedirect
from django.utils.translation import ugettext as _
from django.views.generic.base import TemplateView
from django.views.generic.detail import DetailView
from django.views.generic.edit import (CreateView, UpdateView, DeleteView,
    FormView, FormMixin)
from django.views.generic.list import ListView
from django.contrib import messages
from braces.views import LoginRequiredMixin, UserFormKwargsMixin
from .forms import (AddProjectForm, UpdateUserProfileForm,
    ProjectDependencyForm, ProjectMemberForm, CreateProjectMemberForm,
    UpdateProjectDependencyForm)
from .models import (Project, UserProfile, ProjectDependency, ProjectMember,
    Log, Package)
from .utils.views import SortListMixin, ProjectMixin
# -*- coding: utf-8 -*-





folivora_index = TemplateView.as_view(template_name='folivora/index.html')


class ListProjectView(LoginRequiredMixin, SortListMixin, ListView):
    model = Project
    context_object_name = 'projects'
    sort_fields = ['name']
    default_order = ('name',)

    def get_queryset(self):
        qs = super(ListProjectView, self).get_queryset()
        return qs.filter(members=self.request.user)


project_list = ListProjectView.as_view()


class DashboardView(ListProjectView):
    template_name = 'folivora/dashboard.html'
    sort_fields = ['name', 'updates', 'rl_date']

    def get_queryset(self):
        qs = super(DashboardView, self).get_queryset()
        qs = qs.annotate(updates=models.Count('dependencies__update')) \
           .annotate(rl_date=models.Min('dependencies__update__release_date'))
        return qs

    def get_context_data(self, **kwargs):
        c = super(DashboardView, self).get_context_data(**kwargs)
        # Get the last 5 log entries for all project of the user.
        l = Log.objects
        l = l.filter(project__members=self.request.user).order_by('-when')[:5]
        # Find all updates and group them by package__name (in the template)
        u = ProjectDependency.objects
        u = u.filter(project__members=self.request.user,
                     update__isnull=False) \
             .values('project__name',
                     'package__name',
                     'update__version').order_by('package__name')
        c.update({
            'log_entries': l,
            'updates': u,
        })
        return c

folivora_dashboard = DashboardView.as_view()


class AddProjectView(LoginRequiredMixin, UserFormKwargsMixin, CreateView):
    model = Project
    form_class = AddProjectForm
    template_name_suffix = '_add'

    def form_valid(self, form):
        messages.success(self.request, _('Project created successfully. Please'
            ' give us a minute to sync your project with PyPi.'))
        return super(AddProjectView, self).form_valid(form)


project_add = AddProjectView.as_view()


class UpdateProjectView(ProjectMixin, TemplateView):
    model = Project
    template_name = 'folivora/project_update.html'

    dep_qs = ProjectDependency.objects.select_related('package') \
                                      .order_by('package__name')
    dep_form_class = inlineformset_factory(Project, ProjectDependency, extra=0,
        form=ProjectDependencyForm)

    member_qs = ProjectMember.objects.select_related('user') \
                                     .order_by('user__username')
    member_form_class = inlineformset_factory(Project, ProjectMember, extra=0,
        form=ProjectMemberForm)

    allow_only_owner = True

    def get_context_data(self, **kwargs):
        context = super(UpdateProjectView, self).get_context_data(**kwargs)
        data = self.request.POST if self.request.method == 'POST' else None
        dep_form = self.dep_form_class(data, queryset=self.dep_qs,
                                       instance=self.project)
        member_form = self.member_form_class(data, instance=self.project,
                                             queryset=self.member_qs)
        context.update({
            'dep_form': dep_form,
            'member_form': member_form,
        })
        return context

    def post(self, request, *args, **kwargs):
        ctx = self.get_context_data(**kwargs)
        dep_form = ctx['dep_form']
        member_form = ctx['member_form']

        original_data = ProjectDependency.objects.in_bulk(