Ejemplo n.º 1
0
def import_facts(args):
    """Imports all available facts from Hamster. Checks uniqueness."""
    db = default_storage()
    if args.date:
        start_date = datetime.datetime.strptime(args.date, '%Y-%m-%d').date()
    else:
        imported = HamsterFact.objects(db).order_by('date_time', reverse=True)
        if imported:
            start_date = imported[0].date_time
        else:
            start_date = datetime.date(1980,1,1)

    print('Importing all facts since {0}'.format(start_date))
    if args.dry_run:
        print('(dry run, no data will be actually saved.)')

    imported_cnt = 0

    storage = hamster.client.Storage()

    for data in storage.get_facts(start_date, datetime.date.today()):
        saved = _import_fact(data, dry_run=args.dry_run)
        if saved:
            imported_cnt += 1

    print('Imported {imported_cnt} facts.'.format(**locals()))
    if args.dry_run:
        print('(None was actually saved in dry run mode.)')
Ejemplo n.º 2
0
def _update_fact(data, dry_run=False):
    db = default_storage()
    facts = HamsterFact.objects(db).where(x_hamster_id=int(data['id']))
    if not facts:
        print 'no fact with id', repr(data['id'])
        return False
    assert 1 == len(facts)
    fact = facts[0]
    prepared = _prepare_data(data)
    if prepared['tags'] == []:
        prepared['tags'] = None  # this is how the schema works
    for key in prepared:
        old_value = fact[key]
        if isinstance(old_value, datetime.datetime):
            old_value = pytz.utc.localize(old_value)
        if prepared[key] != old_value:
            print '---', fact
            print 'NOTEQ {0}: {1} vs. {2}'.format(
                key, repr(prepared[key]), repr(fact[key]))
            break
        #print 'EQ:', repr(prepared[key]), 'vs.', repr(fact[key])
    else:
        #print 'SAME', fact
        return False  # same data
    fact.update(**prepared)
    if not dry_run:
        fact.save()
    return fact
Ejemplo n.º 3
0
def find_needs(query=None, exclude=None, project=None, extra=None):
    db = default_storage()
    extra = extra or {}
    if project:
        needs = project.needs
        if needs is None:
            return
        if query:
            # FIXME sud.needs should return a query instead of list (Docu bug)
            needs = (n for n in needs if re.match(ur'.*{0}.*'.format(query),
                                                  n.summary,
                                                  re.UNICODE|re.IGNORECASE))

        # filter by extra conditions - should be done via query API, too
        for k,v in extra.items():
            needs = (n for n in needs if n[k] == v)
        for need in needs:
            yield need
    else:
        needs = Need.objects(db)
        if query:
            needs = needs.where(summary__matches_caseless=query)
        if exclude:
            needs = needs.where_not(summary__matches_caseless=exclude)
        if extra:
            needs = needs.where(**extra)
        for need in needs:
            yield need
Ejemplo n.º 4
0
def plan_index(request):
    db = default_storage()
    plans = Contract.objects(db).order_by("next_date_time")
    return {
        "object_list": plans,
        #        'total_monthly_fee': sum(p.actual_monthly_fee for p in plans)
    }
Ejemplo n.º 5
0
def plan(request, pk):
    db = default_storage()
    # TODO: drop hierarchy, stick to semantics (reference to Need document)
    plan = HierarchicalPlan.object(db, pk)

    # task-related stats
    # TODO aggregate data from nested plans
    durations = (e.get_duration() for e in plan.events if e.get_duration())
    total_duration = sum(durations, datetime.timedelta(0)) # <- initial value

    # financial stats
    # TODO: aggregate data from nested plans
    payments = Payment.objects(db).where(plan=plan)
    total_amount = sum(p.get_amount_as() for p in payments)

    try:
        reasoned = ReasonedPlan.object(db, pk)
        outcome = reasoned.outcome
    except ValidationError:
        outcome = None

    return {
        'object': plan,
        'outcome': outcome,
        'total_duration': total_duration,
        'total_amount': total_amount,
        'render_rel_delta': render_rel_delta,
    }
Ejemplo n.º 6
0
def purge_facts(args):
    """Deletes all facts previously imported from Hamster and not currently
    present there.     WARNING: the command does *NOT* check if the "orphaned"
    facts are in the scope of given Hamster storage. That is, all facts
    gathered from an older storage will be DROPPED. This should be fixed later.
    """
    db = default_storage()
    imported = HamsterFact.objects(db).order_by('date_time')
    storage = hamster.client.Storage()

    seen_cnt = deleted_cnt = 0

    print('Purging orphaned facts...')
    if args.dry_run:
        print('(dry run, no data will be actually updated.)')

    for fact in imported:
        fact_id = int(fact.x_hamster_id)
        try:
            storage.get_fact(fact_id)
        except dbus.exceptions.DBusException:
            # fact is no more in Hamster
            # XXX TODO: check if the Hamster storage is not newer than known
            #           facts!!! if it is, we'll lose older data
            print 'DEL', fact, fact.get_duration()

            # check if the fact can be safely deleted.
            # FIXME this is a quick fix for plan references. We should instead
            # check for *all* attributes (via Document instance) and copy them
            # to the newer fact; if the newer fact cannot be found (e.g.
            # date/time were updated), then just leave it as is.
            # This should *not* apply to the created/updated tmestamps.
            plan_pk = fact.get('plan')
            plan = Plan.object(db, plan_pk) if plan_pk else None
            if plan:
                # try finding exact match by date/time (exact replacement)
                same_facts = imported.where(date_time=fact.date_time)
                same_facts = [f for f in same_facts if f.pk != fact.pk]
                replacement = same_facts[0] if same_facts else None
                if replacement:
                    print('  Copying plan to fact {0}'.format(replacement.pk))
                    assert not replacement.get('plan'), (
                        'the replacing fact must be freshly imported')
                    d = Document.object(db, replacement.pk)
                    d['plan'] = plan.pk
                    if not args.dry_run:
                        d.save()
                        fact.delete()
                    deleted_cnt += 1
                else:
                    print('  Not deleting: fact references plan {0}'.format(plan))
            else:
                if not args.dry_run:
                    fact.delete()
                deleted_cnt += 1
        seen_cnt += 1

    print('Deleted {deleted_cnt} facts of {seen_cnt}.'.format(**locals()))
    if args.dry_run:
        print('(None was actually deleted in dry run mode.)')
Ejemplo n.º 7
0
def show_history(args):
    """Displays events filtered by given query or given date.

    Concerning format: the `Python string formatting` rules apply. For the list
    of available fields see the :class:`orgtool.ext.events.schema.Event`
    document. Also available is keyword "delta".

    .. _Python string formatting: http://docs.python.org/library/string.html
    """
    fix_unicode(args, 'query', 'format')
    # TODO this lists *all* events, whether related to time tracking or not.
    # here we only need a subset of them: the *user's* activities!

    # FIXME
    import warnings
    warnings.warn('FIXME: for some reason "ls|wc -l" and "ls --count" '
                  'return slightly different results')

    db = default_storage()
    events = Event.objects(db).order_by('date_time')
    if args.query:
        events = events.where(summary__matches_caseless=args.query)
    if args.count:
        yield events.count()
    else:
        for event in events:
            template = args.format or u'{date_time} {summary}'
            delta = ''
            if 'delta' in template:
                if event.date_time_end:
                    delta = event.date_time_end - event.date_time
            yield template.format(delta=delta, **event)
Ejemplo n.º 8
0
def notes(request, year=None, month=None):
    db = default_storage()
    notes = Note.objects(db).order_by('date', reverse=True)
    if year:
        notes = notes.where(date__year=year)
        if month:
            notes = notes.where(date__month=month)
    return {'notes': notes}
Ejemplo n.º 9
0
def find_projects(query=None, exclude=None):
    db = default_storage()
    suds = SystemUnderDevelopment.objects(db)
    if query:
        suds = suds.where(summary__matches_caseless=query)
    if exclude:
        suds = suds.where_not(summary__matches_caseless=exclude)
    return suds
Ejemplo n.º 10
0
def bookmark_index(request, tag=None):
    db = default_storage()
    bookmarks = Bookmark.objects(db).order_by('date_time', reverse=True)
    if tag:
        tag = tag.strip().split()
        bookmarks = bookmarks.where(tags__contains=tag)
    if 'q' in request.values:
        # TODO: multiple fields
        bookmarks = bookmarks.where(summary__contains=request.values['q'])
    return {'object_list': bookmarks}
Ejemplo n.º 11
0
def event_index(request, year=None, month=None, day=None):
    db = default_storage()
    events = Payment.objects(db).order_by("date_time", reverse=True)
    if year:
        events = events.where(date_time__year=year)
        if month:
            events = event.where(date_time__month=month)
            if day:
                events = events.where(date_time__day=day)
    return {"object_list": events}
Ejemplo n.º 12
0
def plan_index(request):
    db = default_storage()
    plans = Plan.objects(db).order_by('valid_until', reverse=True)
            #.where(unlocks_plan__in=[None])
            #.order_by('next_date_time')
    return {
        'object_list': plans,
        'render_rel_delta': render_rel_delta,
        'is_date_within_a_day': is_date_within_a_day,
    }
Ejemplo n.º 13
0
def _import_fact(data, dry_run=False):
    db = default_storage()
    assert data.id
    if HamsterFact.objects(db).where(x_hamster_id=int(data.id)).count():
        return False
    prepared = _prepare_data(data)
    fact = HamsterFact(**prepared)
    if not dry_run:
        fact.save(db)
    print 'ADD', fact
    return fact
Ejemplo n.º 14
0
def ls(args):
    "Lists all existing notes."
    db = default_storage()
    notes = Note.objects(db).order_by(['date', 'text'], reverse=True)
    notes_cnt = notes.count()
    if notes_cnt:
        print('There are {0} notes:'.format(notes_cnt))
        print('---')
        for num, note in enumerate(notes):
            print(u'#{num} {date} {text}'.format(num=num, **note))
    else:
        print('There are no notes in the database.')
Ejemplo n.º 15
0
def dashboard(request):
    db = default_storage()
    contracts = Contract.objects(db).order_by("next_date_time")
    payments = Payment.objects(db).order_by("date_time")
    return {
        "contracts": contracts,
        "payments": payments,
        "render_rel_delta": render_rel_delta,
        "chart_url_for_payments": chart_url_for_payments,
        "today": datetime.datetime.today(),
        "default_currency": get_default_currency(),
    }
Ejemplo n.º 16
0
def idea_index(request, tag=None):
    db = default_storage()
    ideas = Idea.objects(db).order_by('date_time', reverse=True)
    if 'q' in request.values:
        # TODO: multiple fields
        ideas = ideas.where(summary__contains=request.values['q'])
    # TODO: generic facet filtering
    if 'is_reviewed' in request.values:
        value = bool(int(request.values['is_reviewed']))
        ideas = ideas.where(is_reviewed=value)
    if 'is_needed' in request.values:
        value = bool(int(request.values['is_needed']))
        ideas = ideas.where(is_needed=value)
    return {'object_list': ideas}
Ejemplo n.º 17
0
def report(request):
    query = Document.objects(default_storage())  # TODO: only docs registered with admin?
    form = CastForm(request.form)
    factors = []
    pivot_factors = []
    aggregate = Count()
    if form.validate():
        factors = form.data.get('factors')
        factors = factors.split(' ') if factors else []
        pivot_factors = form.data.get('pivot_factors')
        pivot_factors = pivot_factors.split(' ') if pivot_factors else []
        aggregate = form.get_aggregate() or Count()
    table = dark.cast(query, factors, pivot_factors, aggregate)
    return {'form': form, 'table': table}
Ejemplo n.º 18
0
def object_list(request, namespace, model_name):
    db = default_storage()
    model = _get_model(namespace, model_name)
    query = model.objects(db)

    if 'q' in request.values:
        value = request.values.get('q')
        # TODO: move defs sanity check elsewhere (admin site function?)
        field_names = env('search_names')[model]
        if not field_names:
            raise ValueError('Cannot search {0} objects: search fields are '
                             'not defined'.format(model.__name__))
        assert isinstance(field_names, (list, tuple))
        # FIXME should be q.where(foo=q).or_where(bar=q)
        # but Docu doesn't support OR at the moment
        if 1 < len(field_names):
            raise NotImplementedError('Multiple search fields are not yet '
                                      'supported in this version.')
        # TODO: pre-convert value?
        query = query.where(**{'{0}__matches_caseless'.format(field_names[0]): value})

    ordering = env('ordering').get(model)
    if 'sort_by' in request.values:
        sort_field = request.values.get('sort_by')
        sort_reverse = bool(request.values.get('sort_reverse', False))
        ordering = dict(ordering, names=[sort_field])
    if ordering:
        query = query.order_by(**ordering)

    #pagin_args =  {'namespace': namespace, 'model_name': model_name}
    #objects, pagination = paginated(query, req, pagin_args)
    page = request.values.get('page', 1)
    per_page = request.values.get('per_page', 20)
    pagination = Pagination(query, per_page, page,
                            'tool.ext.admin.views.object_list',
                            namespace=namespace,
                            model_name=model_name)

    list_names = env('list_names')[model] or ['__unicode__']

    return {
        'namespace': namespace,
        'query': query,
        #'objects': objects,
        'pagination': pagination,
        'list_names': list_names,
        'search_enabled': bool(env('search_names')[model]),
    }
Ejemplo n.º 19
0
def move_contact(args):
    "Moves matching contacts to given actor"
    fix_unicode(args, 'from_actor', 'query', 'type', 'new_actor')
    new_actor = None
    try:
        new_actor = get_single(find_actors, args.new_actor)
    except MultipleMatches as e:
        raise CommandError(e)
    except NotFound as e:
        raise CommandError(u'No actor matches "{0}".'.format(args.actor))

    assert args.from_actor or args.query, 'specify actor or auery'

    db = default_storage()
    contacts = Contact.objects(db).where_not(actor=new_actor.pk)
    if args.type:
        contacts = contacts.where(type__in=args.type)
    if args.query:
        contacts = contacts.where(value__matches_caseless=args.query)
    if args.from_actor:
        try:
            from_actor = get_single(find_actors, args.from_actor)
        except MultipleMatches as e:
            raise CommandError(e)
        except NotFound(e):
            raise CommandError('Bad --from-actor: no match for "{0}"'.format(
                args.from_actor))
        contacts = contacts.where(actor=from_actor.pk)

    if not len(contacts):
        raise CommandError('No suitable contacts were found.')

    yield('About to move these contacts:\n')
    for c in contacts:
        yield(u'- {actor}: {kind} {v}'.format(v=bright(c.value), **c))
    yield('')

    msg = u'Move these contacts to {0}'.format(bright(new_actor))
    if confirm(msg, default=True):
        for c in contacts:
            c.actor = new_actor
            c.save(db)
    else:
        yield('\nCancelled.')
Ejemplo n.º 20
0
def message_index(request, year=None, month=None, day=None):
    db = default_storage()
    messages = Message.objects(db).order_by('date_time', reverse=True)
    if year:
        messages = messages.where(date_time__year=year)
        if month:
            messages = messages.where(date_time__month=month)
            if day:
                messages = messages.where(date_time__day=day)
    for name in 'sent_by', 'sent_to':
        if name in request.values:
            print repr(request.values[name])
            messages = messages.where(**{name: request.values[name]})
#    if 'state' in request.values:
#        messages = messages.where(state=request.values['state'])
    if 'q' in request.values:
        messages = messages.where(summary__contains=request.values['q'])
    print messages._conditions
    return {'object_list': messages}
Ejemplo n.º 21
0
    def make_env(self, **settings):
        if 'secret' not in settings:
            # TODO: add tool.conf.ConfigurationError excepton class
            raise KeyError('Plugin {0} requires setting "secret"'.format(__name__))

        db_label = settings.pop('database', None)
        database = storages.get(db_label) or default_storage()

        if settings.get('config'):
            conf = import_whatever(settings['config'])
            mw_conf = conf(**settings) if hasattr(conf, '__call__') else conf
        else:
            preset = settings.get('preset', 'basic')
            f = KNOWN_PRESETS[preset]
            mw_conf = f(**settings)

        return {
            'middleware_config': mw_conf,
            'database': database,
        }
Ejemplo n.º 22
0
def drop(args):
    """Deletes given notes from the database. If note number is not specified,
    deletes all notes.
    """
    db = default_storage()
    notes = Note.objects(db).order_by(['date', 'text'], reverse=True)
    if args.number:
        choices = dict(enumerate(notes))
        try:
            note = choices[args.number]
        except KeyError:
            print('There is no note #{0}'.format(args.number))
        else:
            note.delete()
            print('Note "{text}" ({date}) has been deleted.'.format(**note))
    elif args.all:
        notes.delete()
        print('All notes have been deleted.')
    else:
        print args
        print('Please specify either --number or --all.')
Ejemplo n.º 23
0
def add_need(request):
    db = default_storage()

    ###
    import wtforms
#    from doqu.ext.forms import document_form_factory
#    BaseNeedForm = document_form_factory(Need, storage=db)
    class NeedForm(wtforms.Form):
        summary = wtforms.TextField('summary')
        stakeholders = wtforms.FieldList(wtforms.TextField('stakeholders'),
            min_entries=1)

    ###

    form = NeedForm(request.values)
    if request.method == 'POST':
        form = NeedForm(request.values)
        if form.validate():
            obj = Need()
            form.populate_obj(obj)
            obj.save(db)
            return redirect_to('needs.need', pk=obj.pk)
    return {'form': form}
Ejemplo n.º 24
0
def add_contact(args):
    "Adds a contact to given actor"
    fix_unicode(args, 'actor', 'value', 'type')
    actor = None
    try:
        actor = get_single(find_actors, args.actor)
    except MultipleMatches as e:
        raise CommandError(e)
    except NotFound as e:
        raise CommandError(u'No actor matches "{0}".'.format(args.actor))

    db = default_storage()
    qs = Contact.objects(db)
    dupes = qs.where(actor=actor, kind=args.type, value=args.value)
    if dupes.count():
        raise CommandError('Such contact already exists.')

    # FIXME Docu doesn't properly assign values from Contact(foo=bar)
    contact = Contact()
    contact.actor = actor
    contact.kind = args.type
    contact.value = args.value
    contact.save(db)
Ejemplo n.º 25
0
def object_detail(request, namespace, model_name, pk=None):
    db = default_storage()
    model = _get_model(namespace, model_name)
    if pk:
        try:
            obj = db.get(model, pk)
        except doqu.validators.ValidationError:
            # Whoops, the data doesn't fit the schema. Let's try converting.
            obj = db.get(Document, pk)
            obj = obj.convert_to(model)
        creating = False
    else:
        obj = model()
        creating = True

    if not creating and request.form.get('DELETE'):
        # TODO: confirmation screen.
        # List related objects, ask what to do with them (cascade/ignore/..)
        obj.delete()
        return redirect_to('tool.ext.admin.views.object_list', namespace=namespace,
                        model_name=model_name)

    DocumentForm = document_form_factory(model, db)

    if not model.meta.structure:
        for k in obj:
            setattr(DocumentForm, k,
                    wtforms.fields.TextField(k.title().replace('_',' ')))

    form = DocumentForm(request.form, obj)

    for name in _get_excluded_names(model):
        del form[name]

    message = None
    if request.method == 'POST' and form.validate():
        form.populate_obj(obj)

        #assert 0==1, 'DEBUG'
        #if not __debug__:

        obj.save(db)    # storage can be omitted if not creating obj

        # TODO: move this to request.session['messages'] or smth like that
        message = u'%s has been saved.' % obj.__class__.__name__
        #if creating:
        # redirect to the same view but change URL
        # from ".../my_model/add/" to
        # to the editing URL ".../my_model/123/"
        # ...hmm, we *should* redirect even after editing an existing item
        return redirect_to('tool.ext.admin.views.object_detail',
                           namespace=namespace, model_name=model_name,
                           pk=obj.pk)
    obj_url = _get_url_for_object(obj)

    # objects of other models that are known to reference this one
    references = {}
    for model, attrs in model.meta.referenced_by.iteritems():
        for attr in attrs:
            qs = model.objects(db).where(**{attr: obj.pk})
            if qs.count():
                references.setdefault(model, {})[attr] = qs

    return {
        'namespace': namespace,
        'object': obj,
        'object_url': obj_url,
        'form': form,
        'message': message,
        'references': references,
        'other_doc_types': env('registered_models'),
    }
Ejemplo n.º 26
0
def need_index(request):
    db = default_storage()
    needs = Need.objects(db).order_by('summary')
    return {
        'object_list': needs,
    }
Ejemplo n.º 27
0
def project(request, pk):
    db = default_storage()
    obj = db.get(SystemUnderDevelopment, pk)
    return {'object': obj}
Ejemplo n.º 28
0
def project_index(request):
    db = default_storage()
    projects = SystemUnderDevelopment.objects(db).order_by('summary')
    return {
        'object_list': projects,
    }
Ejemplo n.º 29
0
def need(request, pk):
    db = default_storage()
    obj = db.get(Need, pk)
    return {'object': obj}
Ejemplo n.º 30
0
#--- projects (project = system under development)

@url('/projects/')
@requires_auth
@entitled(u'Projects')
@as_html('needs/project_index.html')
def project_index(request):
    db = default_storage()
    projects = SystemUnderDevelopment.objects(db).order_by('summary')
    return {
        'object_list': projects,
    }

@url('/projects/<string:pk>')
@requires_auth
@entitled(lambda pk: u'{0}'.format(default_storage().get(SystemUnderDevelopment,pk)))
@as_html('needs/project_detail.html')
def project(request, pk):
    db = default_storage()
    obj = db.get(SystemUnderDevelopment, pk)
    return {'object': obj}


#--- needs

@url('/needs/')
@requires_auth
@entitled(u'Needs')
@as_html('needs/need_index.html')
def need_index(request):
    db = default_storage()