class Document(db.TimeStampedBase): owner = db.ForeignKey(kind=Member) logo = db.Binary() template = db.ForeignKey(kind=Template) name = db.String() injections = db.JSON() assembly = db.JSON() declarations = db.JSON() pdf = db.String() revision = db.Integer(default=0) signup_sheet = db.Boolean(default=True) table_of_contents = db.Boolean(default=True) declaration_page = db.Boolean(default=True) pretty_filenames = db.Boolean(default=True) section_page_breaks = db.Boolean(default=False) def summary(self): d = { "key": self.id(), "name": self.name, "revision": self.revision, "pdf": self.pdf, "created": str(self.created)[:19], "modified": str(self.modified)[:19], "declarations": self.declarations or {} } if self.template: t = self.template.get() d["template"] = {"key": t.id(), "name": t.name} return d def content(self, sections=None): return self.template.get().content( sections, page_breaks=self.section_page_breaks)
class Contribution(db.TimeStampedBase): codebase = db.ForeignKey(kind=Codebase) contributor = db.ForeignKey(kind=Contributor) count = db.Integer(default=0) def handle(self): return self.contributor.get().handle def membership(self): from .core import Person, Membership person = Person.query( Person.contributors.contains(self.contributor.urlsafe())).get() pod = db.get(self.codebase).pod return person and pod and Membership.query( Membership.pod == pod, Membership.person == person.key).get() def total(self): return self.count * ratios.code.line def process(self, cbatch, diff, total=0): cbase = self.codebase.get() self.membership().deposit( diff * ratios.code.line, cbatch, "code commits: %s@%s" % (self.handle(), cbase.repo), "variety: %s\nowner: %s\nbatch: %s\ntotal: %s" % (cbase.variety, cbase.owner, diff, total or diff), True) def refresh(self, total, cbatch): diff = total - self.count if diff: self.process(cbatch, diff, total) self.count = total self.put() return diff
class Resource(Place): editors = db.ForeignKey(kind="Person", repeated=True) name = db.String() description = db.Text() tags = db.ForeignKey(kind=Tag, repeated=True) icon = db.String() # refers to ctmap graphic resource label = "name" def _pre_trans_zipcode(self, val): if isinstance(val, string_types) and len(val) < 10: val = getzip(val).key return val def total(self): return config.ctcomp.ratios.resource def oncreate(self): zcode = self.zipcode.get() addr = "%s, %s, %s" % (self.address, zcode.city, zcode.state) self.latitude, self.longitude = address2latlng(addr) def notify(self, podname, interested): bod = RESOURCE % (podname, self.name, self.description) for person in interested: send_mail(to=person.email, subject="new message board", body=bod)
class Stewardship(db.TimeStampedBase): steward = db.ForeignKey() timeslots = db.ForeignKey(kind=Timeslot, repeated=True) def task(self): return Task.query(Task.commitments.contains(self.key.urlsafe())).get() def happening(self, now): slots = [] if self.task().happening(now): for slot in db.get_multi(self.timeslots): if isDay(slot, now): slots.append(slot) if len(slots) is 1: # if 2, one is exception return slots[0] def beforeremove(self, session): task = self.task() if task: # no task is task is deleting itself... task.unsteward(self) task.commitments = list( filter(lambda x: x != self.key, task.commitments)) task.put(session) def afterremove(self, session): db.delete_multi(db.get_multi(self.timeslots, session), session)
class Codebase(db.TimeStampedBase): pod = db.ForeignKey(kind="Pod") owner = db.String() # bubbleboy14 repo = db.String() # ctcomp variety = db.String(choices=[ "platform", "framework", "service", "research and development" ]) dependencies = db.ForeignKey(kind="Codebase", repeated=True) label = "repo" def deposit(self, amount, deed): log('compensating "%s/%s" codebase: %s' % (self.owner, self.repo, amount)) contz = self.contributions() total = float(sum([cont.count for cont in contz])) platcut = amount * ratios.code.get(self.variety, ratios.code.rnd) log('dividing %s cut (%s) among %s contributors' % (self.variety, platcut, len(contz))) details = "variety: %s\nowner: %s" % (self.variety, self.owner) for contrib in contz: memship = contrib.membership() memship and memship.deposit( platcut * contrib.count / total, deed, "code usage: %s@%s" % (contrib.handle(), self.repo), details, True) depcut = amount * ratios.code.dependency dnum = len(self.dependencies) if dnum: depshare = depcut / dnum log('dividing dependency cut (%s) among %s codebases' % (depcut, dnum)) for dep in db.get_multi(self.dependencies): dep.deposit(depshare, deed) def contributions(self, asmap=False): clist = Contribution.query(Contribution.codebase == self.key).fetch() if not asmap: return clist contz = {} for cont in clist: contz[cont.contributor.get().handle] = cont return contz def refresh(self, cbatch): from .util import getContribution freshies = fetch("api.github.com", "/repos/%s/%s/contributors" % (self.owner, self.repo), asjson=True, protocol="https") pcount = 0 ccount = 0 for item in freshies: log("checking for: %s" % (item["login"], ), 1) contrib = getContribution(self, item["login"]) if contrib: pcount += 1 ccount += contrib.refresh(item["contributions"], cbatch) return "%s/%s: %s contributors, %s contributions" % ( self.owner, self.repo, pcount, ccount)
class Contactable(db.TimeStampedBase): tags = db.ForeignKey(kind=Tag, repeated=True) member = db.ForeignKey() name = db.String() email = db.String() phone = db.String() address = db.String() description = db.Text() # only required field closed = db.Boolean(default=False) ongoing = db.Boolean(default=False)
class LibItem(db.TimeStampedBase): content = db.ForeignKey(kind="Content") editors = db.ForeignKey(kind="Person", repeated=True) name = db.String() description = db.Text() tags = db.ForeignKey(kind=Tag, repeated=True) label = "name" def notify(self, podname, interested): bod = LIBITEM % (podname, self.name, self.description) for person in interested: send_mail(to=person.email, subject="new message board", body=bod)
class Feedback(db.TimeStampedBase): person = db.ForeignKey(kind=Person) conversation = db.ForeignKey(kind=Conversation) interaction = db.ForeignKey(kinds=[Appointment, Delivery, Request]) answers = db.ForeignKey(kind=Answer, repeated=True) topic = db.String() notes = db.Text() followup = db.Boolean(default=False) def membership(self): from .util import membership return membership(self.person.get(), self.pod()) def pod(self): return self.interaction.get().pod() def full(self): answers = "\n\n".join([a.full() for a in db.get_multi(self.answers)]) return "\n\n".join([ self.topic, answers, self.notes, "request follow up: %s" % (self.followup, ) ]) def notify(self): bod = FEEDBACK % (self.person.get().firstName, self.pod().name, self.full(), self.key.urlsafe()) self.interaction.get().notify("feedback", lambda signer: bod, self.participants()) def participants(self): pars = self.interaction.get().signers() if self.person not in pars: return pars + [self.person] return pars def oncreate(self): convo = Conversation(topic=self.topic) convo.participants = self.participants() convo.put() self.conversation = convo.key self.put() # for notify() key self.notify() if self.followup: req = Request() req.membership = self.membership().key req.change = "conversation" req.notes = self.notes req.put() req.remind()
class Membership(db.TimeStampedBase): pod = db.ForeignKey(kind=Pod) person = db.ForeignKey(kind=Person) proposals = db.ForeignKey(kind=Proposal, repeated=True) products = db.ForeignKey(kind=Product, repeated=True) def deposit(self, amount, deed, note, details=None, nocode=False, pay=False): self.pod.get().deposit(self.person.get(), amount, deed, note, details, nocode, pay)
class Expense(Verifiable): executor = db.ForeignKey(kind="Person") # reimbursement only variety = db.String(choices=["dividend", "reimbursement"]) amount = db.Float(default=0.1) # for dividend, split amount * total recurring = db.Boolean(default=False) def dividend(self): pod = self.pod() pool = pod.pool.get() people = db.get_multi(pod.members()) div = self.amount * pool.outstanding cut = div / len(people) for person in people: person.wallet.get().deposit(cut, pod, self, "dividend") pool.debit(div, pod, self, "dividend") # reimbursement requires $$ conversion... def reimbursement(self): pass def fulfill(self): if (self.passed and not self.recurring) or not self.verified(): return False getattr(self, self.variety)() return True
class Payment(Verifiable): payer = db.ForeignKey(kind="Person") amount = db.Float() def signers(self): return [self.payer] def fulfill(self): if self.passed or not self.verified(): return False payer = self.payer.get() memship = self.membership.get() recip = memship.person.get() pod = memship.pod.get() payer.wallet.get().debit(self.amount, pod, self, "payment to %s" % (recip.email, ), self.notes) memship.deposit(self.amount, self, "payment from %s" % (payer.email, ), self.notes, pay=True) self.passed = True self.put() body = PAID % (self.amount, payer.email, recip.email, pod.name, self.notes) for target in [payer, recip]: send_mail(to=target.email, subject="payment confirmation", body=body) return True
class Verifiable(db.TimeStampedBase): membership = db.ForeignKey(kind="Membership") passed = db.Boolean(default=False) notes = db.Text() def pod(self, noget=False): pod = self.membership.get().pod return noget and pod or pod.get() def signers(self): return self.pod().members() def fulfill(self): if not self.verified(): return False self.passed = True self.put() return True def notify(self, subject, body, signers=None): for signer in (signers or self.signers()): if self.unverified(signer): send_mail(to=signer.get().email, subject=subject, body=body(signer)) def unverify(self): log("unverifying %s" % (self.key.urlsafe(), )) sigs = Verification.query(Verification.act == self.key).fetch() log("unsigning %s verifications" % (len(sigs), ), 1) db.delete_multi(sigs) log("unpassing", 1) self.passed = False self.put() def verify(self, person): if person in self.signers(): if Verification.query(Verification.act == self.key, Verification.person == person).get(): return log("already verified (%s %s)!" % (self.key, person), important=True) log("verification (%s %s) success" % (self.key.urlsafe(), person.urlsafe())) Verification(act=self.key, person=person).put() return self.fulfill() log("verification attempt (%s %s) failed -- unauthorized" % (self.key, person)) def veriquery(self): return Verification.query(Verification.act == self.key) def unverified(self, person): return not self.veriquery().filter(Verification.person == person).get() def verified(self): for person in self.signers(): if self.unverified(person): return False return True
class View(db.TimeStampedBase): viewer = db.ForeignKey(kind=Person) content = db.ForeignKey(kind=Content) def process(self): content = self.content.get() mships = content.membership and [content.membership ] or content.memberships cut = ratios.view / len(mships) for memship in mships: membership = memship.get() membership.pod.get().deposit(membership.person.get(), cut, self, "viewed: %s" % (content.identifier, ), "view: %s" % (self.key.urlsafe(), )) def total(self): return ratios.view
class Act(Verifiable): service = db.ForeignKey(kind="Service") workers = db.ForeignKey(kind="Person", repeated=True) beneficiaries = db.ForeignKey(kind="Person", repeated=True) def signers(self): return self.beneficiaries def fulfill(self): if self.passed or not self.verified(): return False count = len(self.beneficiaries) pod = self.pod() service = self.service.get() for worker in db.get_multi(self.workers): pod.service(worker, service, count, self.notes) self.passed = True self.put() return True
class Update(db.TimeStampedBase): sender = db.ForeignKey() subject = db.String() message = db.Text() recipients = db.ForeignKey(repeated=True) conversation = db.ForeignKey(kind=Conversation) label = "subject" def oncreate(self): convo = Conversation(topic=self.subject) convo.put() self.conversation = convo.key if self.recipients: recipients = db.get_multi(self.recipients) else: recipients = Member.query().all() bod = UPDATE % (self.sender.get().email, self.message) for recip in recipients: send_mail(to=recip.email, subject=self.subject, body=bod)
class Commitment(Verifiable): service = db.ForeignKey(kind="Service") estimate = db.Float(default=1.0) # per week (hours?) def deposit(self, numdays=1): service = self.service.get() details = "compensating commitment: %s service (%s); estimated %s hours per week; paying for %s days" % ( service.name, service.compensation, self.estimate, numdays) log(details) self.membership.get().deposit( service.compensation * self.estimate * numdays / 7.0, self, "commitment: %s" % (service.name, ), details)
class Board(db.TimeStampedBase): name = db.String() description = db.Text() anonymous = db.Boolean(default=False) tags = db.ForeignKey(kind=Tag, repeated=True) conversation = db.ForeignKey(kind=Conversation) label = "name" def pod(self): from .core import Pod return Pod.query(Pod.boards.contains(self.key.urlsafe())).get() def notify(self, podname, interested): bod = BOARD % (podname, self.name, self.description) for person in interested: send_mail(to=person.email, subject="new message board", body=bod) def oncreate(self): convo = Conversation(topic=self.name) convo.anonymous = self.anonymous convo.put() self.conversation = convo.key
class Task(db.TimeStampedBase): editors = db.ForeignKey(repeated=True) timeslots = db.ForeignKey(kind=Timeslot, repeated=True) commitments = db.ForeignKey(kind=Stewardship, repeated=True) name = db.String() description = db.Text() mode = db.String() # arbitrary requirements = db.String(repeated=True) steps = db.String(repeated=True) def happening(self, now): slots = [] for slot in db.get_multi(self.timeslots): if isDay(slot, now): slots.append(slot) if len(slots) is 1: # if 2, one is exception return slots[0] def unsteward(self, stewardship, verb="rescheduled"): # just a notifier send_mail(to=stewardship.steward.get().email, subject="commitment update", body=RESCHED % (self.name, verb)) def downschedule(self): stewz = db.get_multi(self.commitments) for stew in stewz: self.unsteward(stew, "rescheduled") self.commitments = [] self.put() db.delete_multi(stewz) def beforeremove(self, session): for stew in db.get_multi(self.commitments, session): self.unsteward(stew, "removed") def afterremove(self, session): db.delete_multi( db.get_multi(self.timeslots + self.commitments, session), session)
class Delivery(Verifiable): driver = db.ForeignKey(kind="Person") miles = db.Integer() def signers(self): return [self.membership.get().person, self.driver] def fulfill(self): if self.passed or not self.verified(): return False self.pod().deposit(self.driver.get(), ratios.delivery + ratios.mileage * self.miles, self, "delivery: %s miles" % (self.miles, ), self.notes) self.passed = True self.put() return True
class SecBase(db.TimeStampedBase): name = db.String() description = db.Text() sections = db.ForeignKey(kind="section", repeated=True) def secs(self, sections=None, depth=0, novars=False, page_breaks=False): return "\r\n\r\n".join(sections and [ db.get(s['key']).content(s['sections'], depth, novars, page_breaks) for s in sections ] or [ s.content(depth=depth, novars=novars, page_breaks=page_breaks) for s in db.get_multi(self.sections) ]) def fixed_desc(self, depth=0, novars=False): d = self.description return h2l(novars and d.replace("{{", "(").replace("}}", ")") or d, depth) def desc(self, depth=0, novars=False): return self.fixed_desc(depth, novars) def header(self): return self.name def body(self, depth, novars=False, page_breaks=False): tline = "%s %s" % ("#" * depth, self.header()) if page_breaks and depth == 1: tline = "\\newpage%s" % (tline, ) return "%s\r\n\r\n%s" % (tline, self.desc(depth, novars)) def content(self, sections=None, depth=0, novars=False, page_breaks=False): body = self.body(depth, novars, page_breaks) secs = self.sections and self.secs(sections, depth + 1, novars, page_breaks) or "" cont = "%s\r\n\r\n%s" % (body, secs) log(cont) return cont def unrolled(self): d = self.data() d['sections'] = [s.unrolled() for s in db.get_multi(self.sections)] return d
class Invitation(db.TimeStampedBase): membership = db.ForeignKey(kind=Membership) email = db.String() notes = db.Text() def invite(self): memship = self.membership.get() send_mail(to=self.email, subject="invitation", body=INVITATION % (memship.person.get().email, memship.pod.get().name)) def send(self, person): req = Request() req.membership = self.membership req.person = person.key req.change = "include" req.notes = self.notes req.put() req.remind()
class Appointment(Verifiable): timeslot = db.ForeignKey(kind=Timeslot) def signers(self): return self.task().editors def task(self): return self.stewardship().task() def stewardship(self): return self.timeslot.get().slotter() def fulfill(self): if not self.verified(): return False self.membership.get().deposit(self.timeslot.get().duration, self, "appointment: %s" % (self.task().name, ), self.notes) self.passed = True self.put() return True
class BasePost(db.TimeStampedBase): user = db.ForeignKey() # CTUser, Author, or whatever else live = db.Boolean(default=False) title = db.String() blurb = db.String() tags = db.String(repeated=True)
class Payment(db.TimeStampedBase): member = db.ForeignKey(kind=Member) successful = db.Boolean(default=False) amount = db.String() duration = db.Integer() # days message = db.Text()
class Template(SecBase): owner = db.ForeignKey(kind=Member) injections = db.ForeignKey(kind=Injection, repeated=True) def body(self, depth, novars=False, page_breaks=False): return self.desc(depth, novars)
class Pod(db.TimeStampedBase): name = db.String() variety = db.String() blurb = db.Text() pool = db.ForeignKey(kind=Wallet) agent = db.ForeignKey(kind="Pod") needs = db.ForeignKey(kind=Need, repeated=True) tasks = db.ForeignKey(kind=Task, repeated=True) boards = db.ForeignKey(kind=Board, repeated=True) updates = db.ForeignKey(kind=Update, repeated=True) drivers = db.ForeignKey(kind=Person, repeated=True) includers = db.ForeignKey(kind=Person, repeated=True) resources = db.ForeignKey(kind=Resource, repeated=True) offerings = db.ForeignKey(kind=Offering, repeated=True) dependencies = db.ForeignKey(kind=Codebase, repeated=True) # software library = db.ForeignKey(kinds=[Organization, Book, Web, Media], repeated=True) # support def _trans_boards(self, val): v = val[-1].get() v.notify(self.name, self.interested(v.tags)) return val def _trans_library(self, val): v = val[-1].get() v.notify(self.name, self.interested(v.tags)) return val def _trans_resources(self, val): v = val[-1].get() v.notify(self.name, self.interested(v.tags)) return val def _trans_needs(self, val): self.notify(val[-1].get(), NEED) return val def _trans_offerings(self, val): self.notify(val[-1].get(), OFFERING) return val def notify(self, item, etemp): bod = etemp % (self.name, item.description) for person in self.interested(item.tags): send_mail(to=person.email, subject="new %s" % (item.polytype, ), body=bod) def interested(self, tags): tagz = set(map(lambda t: t.urlsafe(), tags)) return filter( lambda p: tagz.intersection( set(map(lambda t: t.urlsafe(), p.interests))), db.get_multi(self.members())) def oncreate(self): email_admins("New Pod", "name: %s\nvariety: %s" % (self.name, self.variety)) if not self.pool: w = Wallet() w.put() self.pool = w.key def codebases(self): return Codebase.query(Codebase.pod == self.key).fetch() def _collection(self, mod): return sum([ mod.query(mod.membership == m.key).fetch() for m in self.members(True) ], []) def expenses(self): return self._collection(Expense) def acts(self): return self._collection(Act) def requests(self): return self._collection(Request) def commitments(self): return self._collection(Commitment) def proposals(self): return sum([m.proposals for m in self.members(True)], []) def members(self, noperson=False): mems = Membership.query(Membership.pod == self.key).fetch() return noperson and mems or [mem.person for mem in mems] def deposit(self, member, amount, deed, note, details=None, nocode=False, pay=False): memwall = member.wallet.get() if pay: memcut = amount * ratios.pay amount -= memcut memwall.deposit(memcut, self, deed, note, details) else: memwall.deposit(amount, self, deed, note, details) self.pool.get().deposit(amount, self, deed, note, details) self.agent and self.agent.get().pool.get().deposit( amount * ratios.agent, self, deed, note, details) if not nocode: for codebase in self.codebases(): codebase.deposit(amount, deed) depcut = amount * ratios.code.dependency for dependency in db.get_multi(self.dependencies): dependency.deposit(depcut, deed) def service(self, member, service, recipient_count, details): self.deposit(member, service.compensation * recipient_count, service, "service: %s (%s)" % (service.name, service.variety), details) def support_service(self): sname = (self.variety == "support") and self.name or "support" service = Service.query(Service.name == sname, Service.variety == self.variety).get() if not service: service = Service(name=sname, variety=self.variety) service.put() return service.key
class PhotoSet(BasePost): photos = db.ForeignKey(repeated=True, kind=Photo)
class Content(db.TimeStampedBase): membership = db.ForeignKey(kind=Membership) memberships = db.ForeignKey(kind=Membership, repeated=True) identifier = db.String() # some hash, defaulting to url
class Comment(db.TimeStampedBase): user = db.ForeignKey() # CTUser, Author, or whatever else post = db.ForeignKey(kinds=["post", "videopost", "photoset"]) body = db.Text()
class Person(Member): ip = db.String() # optional wallet = db.ForeignKey(kind=Wallet) # optional interests = db.ForeignKey(kind=Tag, repeated=True) contributors = db.ForeignKey(kind=Contributor, repeated=True) chat = db.Boolean(default=True) remind = db.Boolean(default=True) def onjoin(self): from .util import global_pod email_admins("New Person", self.email) self.enroll(global_pod()) wallet = Wallet() wallet.put() self.wallet = wallet.key self.put() self.process_invites() def process_invites(self): log("processing invitations for %s" % (self.email, )) podz = set() for invitation in Invitation.query( Invitation.email == self.email).fetch(): imem = invitation.membership.get() ipod = imem.pod.get().name memem = imem.person.get().email log("pod: %s. inviter: %s" % (ipod, memem), 1) if ipod in podz: log("skipping invitation -- already invited to pod", 2) else: log("sending invitation", 2) podz.add(ipod) invitation.send(self) def help_match(self, item): # overrides Member.help_match() in ctcoop.model from .util import membership, reg_act which = item.polytype isneed = which == "need" pod = Pod.query( getattr(Pod, which + "s").contains(item.key.urlsafe())).get() reg_act( membership(self, pod).key, pod.support_service(), [isneed and self.key or item.member], [isneed and item.member or self.key], item.description) def enroll(self, pod): from .util import membership memship = membership(self, pod) if not memship: memship = Membership(pod=pod.key, person=self.key) memship.put() return memship.key def tasks(self): return db.get_multi(sum([p.tasks for p in self.pods()], [])) def pods(self): return db.get_multi([m.pod for m in self.memberships()]) def memberships(self): return Membership.query(Membership.person == self.key).fetch() def acts(self): yesterday = datetime.now() - timedelta(1) return sum([ Act.query(Act.membership == m.key, Act.created > yesterday).fetch() for m in self.memberships() ], []) def commitments(self): return sum([ Commitment.query(Commitment.membership == m.key).fetch() for m in self.memberships() ], [])