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.)')
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
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
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) }
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, }
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.)')
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)
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}
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
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}
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}
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, }
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
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.')
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(), }
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}
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}
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]), }
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.')
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}
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, }
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.')
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}
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)
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'), }
def need_index(request): db = default_storage() needs = Need.objects(db).order_by('summary') return { 'object_list': needs, }
def project(request, pk): db = default_storage() obj = db.get(SystemUnderDevelopment, pk) return {'object': obj}
def project_index(request): db = default_storage() projects = SystemUnderDevelopment.objects(db).order_by('summary') return { 'object_list': projects, }
def need(request, pk): db = default_storage() obj = db.get(Need, pk) return {'object': obj}
#--- 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()