~sircmpwn/names.sr.ht

d778737fd98e06b582e61c1d741cc641c0135788 — Drew DeVault 5 months ago 4d9f61e
Implement contact creation, populate contact list
M names/blueprints/contacts.py => names/blueprints/contacts.py +126 -4
@@ 1,14 1,29 @@
import iso3166
from flask import Blueprint, render_template, request
from srht.oauth import loginrequired
import phonenumbers
from flask import Blueprint, render_template, request, redirect, url_for
from names.types import DomainContact
from srht.database import db
from srht.flask import paginate_query
from srht.oauth import current_user, loginrequired
from srht.validation import Validation
from uuid import uuid4

contacts = Blueprint("names.contacts", __name__)

@contacts.route("/contacts")
@loginrequired
def contacts_GET():
    return render_template("contacts.html")
    contacts = (DomainContact.query
            .filter(DomainContact.user_id == current_user.id))
    contacts, pagination = paginate_query(contacts)
    def format_number(n):
        country, number = n.split(".")
        n = phonenumbers.parse(f"+{country} {number}")
        return phonenumbers.format_number(n,
                phonenumbers.PhoneNumberFormat.INTERNATIONAL)
    return render_template("contacts.html",
            contacts=contacts, iso3166=iso3166, format_number=format_number,
            **pagination)

@contacts.route("/contacts/new")
@loginrequired


@@ 20,4 35,111 @@ def new_GET():
@loginrequired
def new_POST():
    valid = Validation(request)
    return "thanks"
    first_name = valid.require("first_name", friendly_name="First name")
    last_name = valid.require("last_name", friendly_name="Last name")
    org_name = valid.require("org_name", friendly_name="Organization name")
    email = valid.require("email", friendly_name="Email address")
    address1 = valid.require("address1", friendly_name="Address")
    address2 = valid.optional("address2")
    address3 = valid.optional("address3")
    city = valid.require("city", friendly_name="City")
    state = valid.optional("state")
    post_code = valid.require("post_code", friendly_name="Post code or zip code")
    country = valid.require("country", friendly_name="Country")
    phone = valid.require("phone", friendly_name="Phone number")
    fax = valid.optional("fax")
    lang = valid.optional("lang")
    vat = valid.optional("vat")
    notes = valid.optional("notes")
    if not valid.ok:
        print(valid.response)
        return render_template("new-contact.html",
                iso3166=iso3166, **valid.kwargs)

    valid.expect(len(first_name) <= 64,
            "First name must be no more than 64 characters",
            field="first_name")
    valid.expect(len(last_name) <= 64,
            "Last name must be no more than 64 characters",
            field="last_name")
    valid.expect(len(org_name) <= 64,
            "Organization name must be no more than 64 characters",
            field="org_name")
    valid.expect(len(address1) <= 64
            and (not address2 or len(address2) <= 64)
            and (not address3 or len(address3) <= 64),
            "Address lines must be no more than 64 characters each",
            field="address1")
    valid.expect(len(city) <= 64,
            "City must be less than 64 characters", field="city")
    valid.expect(not state or len(state) <= 32,
            "State or Province must be no more than 32 characters",
            field="state")
    valid.expect(country not in ["US", "CA"] or state,
            "State or Province is required for US and Canadian contacts",
            field="state")
    valid.expect(len(post_code) <= 16,
            "Postal code or zip code must be no more than 16 characters",
            field="")
    valid.expect(country in iso3166.countries_by_alpha2,
            "Invalid country selected", field="country")
    try:
        phone = phonenumbers.parse(phone, country)
        valid.expect(phonenumbers.is_valid_number(phone),
                "This phone number is invalid", field="phone")
        phone = (f"{phone.country_code}.{phone.national_number}" +
                (f"x{phone.extension}" if phone.extension else ""))
    except phonenumbers.NumberParseException:
        valid.error("Invalid phone number", field="phone")
    if fax:
        try:
            fax = phonenumbers.parse(fax, country)
            valid.expect(phonenumbers.is_valid_number(fax),
                    "This phone number is invalid", field="fax")
            fax = (f"{fax.country_code}.{fax.national_number}" +
                    (f"x{fax.extension}" if fax.extension else ""))
        except phonenumbers.NumberParseException:
            valid.error("Invalid phone number", field="fax")
    valid.expect("@" in email, "Invalid email address", field="email")
    if not valid.ok:
        print(valid.response)
        return render_template("new-contact.html",
                iso3166=iso3166, **valid.kwargs)

    contact = DomainContact()
    contact.uuid = str(uuid4())
    contact.user_id = current_user.id
    contact.first_name = first_name
    contact.last_name = last_name
    contact.org_name = org_name
    contact.address1 = address1
    contact.address2 = address2
    contact.address3 = address3
    contact.city = city
    contact.state = state
    contact.postal_code = post_code
    contact.country = country
    contact.phone = phone
    contact.fax = fax
    contact.email = email
    contact.lang = lang
    contact.vat = vat
    contact.note = notes
    db.session.add(contact)
    db.session.flush()

    use_owner = valid.optional("use_owner")
    use_admin = valid.optional("use_admin")
    use_billing = valid.optional("use_billing")
    use_tech = valid.optional("use_tech")
    if use_owner == "on":
        current_user.default_owner_id = contact.id
    if use_admin == "on":
        current_user.default_admin_id = contact.id
    if use_billing == "on":
        current_user.default_billing_id = contact.id
    if use_tech == "on":
        current_user.default_tech_id = contact.id

    db.session.commit()
    return redirect(url_for("names.contacts.contacts_GET"))

M names/templates/contacts.html => names/templates/contacts.html +24 -47
@@ 4,41 4,49 @@
  <div class="col-md-8">
    <h3>Manage contacts</h3>
    <div class="event-list" style="margin-top: 0.5rem">
      {% for contact in contacts %}
      <div class="event">
        <h4>
          John Doe &mdash; Acme, Inc
          {{contact.first_name}} {{contact.last_name}}
          &mdash;
          {{contact.org_name}}
          <small class="text-muted pull-right">
            b20968c6-8b99-42a0-8d80-da35eb4d945c
            {{contact.uuid}}
          </small>
        </h4>
        <address class="contact row">
          <dl class="col-md-6">
            <dt>Address</dt>
            <dd>
              123 Main St.<br />
              Suite 1337<br />
              Philadephia, PA 19102<br />
              United States
              {{contact.address1}}<br />
              {% if contact.address2 %}
              {{contact.address2}}<br />
              {% endif %}
              {% if contact.address3 %}
              {{contact.address3}}<br />
              {% endif %}
              {{contact.city}}, {{contact.state}} {{contact.post_code}}<br />
              {{iso3166.countries_by_alpha2[contact.country].name}}
            </dd>
          </dl>
          <dl class="col-md-6">
            <dd><a href="#">jdoe@example.org</a></dd>
            <dd><a href="mailto:{{contact.email}}">{{contact.email}}</a></dd>
            <dt>Phone</dt>
            <dd>+1 (201) 123-4321</dd>
            <dd>{{format_number(contact.phone)}}</dd>
            {% if contact.fax %}
            <dt>Fax</dt>
            <dd>+1 (201) 123-4321 x222</dd>
            <dd>{{format_number(contact.fax)}}</dd>
            {% endif %}
          </dl>
        </address>
        {% if contact.note %}
        <div class="row" style="margin-bottom: 1rem">
          <dl class="col-md-12">
            <dt>Notes</dt>
            <dd>
              This is the contact used for most domains we manage on behalf of
              Acme, Inc. The customer has asked not to update it without a
              request in writing, for security reasons.
            </dd>
            <dd>{{contact.note}}</dd>
          </dl>
        </div>
        {% endif %}
        <div class="row">
          <div class="col-md-3 offset-md-9">
            <button class="btn btn-block btn-default">


@@ 47,39 55,8 @@
          </div>
        </div>
      </div>
      <div class="event">
        <h4>
          Jane Doe &mdash; Acme, Inc
          <small class="text-muted pull-right">
            b20968c6-8b99-42a0-8d80-da35eb4d945c
          </small>
        </h4>
        <address class="contact row">
          <dl class="col-md-6">
            <dt>Address</dt>
            <dd>
              123 Main St.<br />
              Suite 1337<br />
              Philadephia, PA 19102<br />
              United States
            </dd>
          </dl>
          <dl class="col-md-6">
            <dd><a href="#">jdoe@example.org</a></dd>
            <dt>Phone</dt>
            <dd>+1 (201) 123-4321</dd>
            <dt>Fax</dt>
            <dd>+1 (201) 123-4321 x222</dd>
          </dl>
        </address>
        <div class="row">
          <div class="col-md-3 offset-md-9">
            <button class="btn btn-block btn-default">
              Edit {{icon("caret-right")}}
            </button>
          </div>
        </div>
      </div>
      {% endfor %}
      {{pagination()}}
    </div>
    <h3>Top-level domain specific</h3>
    <div class="event-list">

M names/templates/new-contact.html => names/templates/new-contact.html +30 -35
@@ 30,6 30,7 @@
        maxlength="64"
        required
        value="{{first_name or ""}}" />
      {{valid.summary("first_name")}}
    </div>
    <div class="col-md-4 form-group">
      <label for="last_name">Last Name</label>


@@ 42,9 43,6 @@
        maxlength="64"
        required
        value="{{last_name or ""}}" />
    </div>
    <div class="col-md-8 offset-md-2">
      {{valid.summary("first_name")}}
      {{valid.summary("last_name")}}
    </div>
    <div class="col-md-8 offset-md-2 form-group">


@@ 58,8 56,6 @@
        maxlength="64"
        required
        value="{{org_name or ""}}" />
    </div>
    <div class="col-md-8 offset-md-2">
      {{valid.summary("org_name")}}
    </div>
    <div class="col-md-8 offset-md-2 form-group">


@@ 72,47 68,43 @@
        placeholder="Email address"
        maxlength="128"
        required
        value="{{post_code or current_user.email}}" />
    </div>
    <div class="col-md-8 offset-md-2">
        value="{{email or current_user.email}}" />
      {{valid.summary("email")}}
    </div>
    <div class="col-md-8 offset-md-2 form-group">
      <label for="address_1">Address</label>
      <label for="address1">Address</label>
      <input
        type="text"
        id="address_1"
        name="address_1"
        class="form-control {{valid.cls("address_1")}}"
        id="address1"
        name="address1"
        class="form-control {{valid.cls("address1")}}"
        placeholder="Address"
        maxlength="64"
        required
        value="{{address_1 or ""}}" />
        value="{{address1 or ""}}" />
      {{valid.summary("address1")}}
    </div>
    <div class="col-md-8 offset-md-2 form-group">
      <input
        type="text"
        id="address_2"
        name="address_2"
        class="form-control {{valid.cls("address_2")}}"
        id="address2"
        name="address2"
        class="form-control {{valid.cls("address2")}}"
        placeholder="Address (continued, optional)"
        maxlength="64"
        value="{{address_2 or ""}}" />
        value="{{address2 or ""}}" />
      {{valid.summary("address2")}}
    </div>
    <div class="col-md-8 offset-md-2 form-group">
      <input
        type="text"
        id="address_3"
        name="address_3"
        class="form-control {{valid.cls("address_3")}}"
        id="address3"
        name="address3"
        class="form-control {{valid.cls("address3")}}"
        placeholder="Address (continued, optional)"
        maxlength="64"
        value="{{address_3 or ""}}" />
    </div>
    <div class="col-md-8 offset-md-2">
      {{valid.summary("address_1")}}
      {{valid.summary("address_2")}}
      {{valid.summary("address_3")}}
        value="{{address3 or ""}}" />
      {{valid.summary("address3")}}
    </div>
    <div class="col-md-4 offset-md-2 form-group">
      <label for="city">City</label>


@@ 125,6 117,7 @@
        maxlength="64"
        required
        value="{{city or ""}}" />
      {{valid.summary("city")}}
    </div>
    <div class="col-md-4 form-group">
      <label for="state">State or Province</label>


@@ 136,9 129,6 @@
        placeholder="State or Province"
        maxlength="32"
        value="{{state or ""}}" />
    </div>
    <div class="col-md-8 offset-md-2">
      {{valid.summary("city")}}
      {{valid.summary("state")}}
    </div>
    <div class="col-md-4 offset-md-2 form-group">


@@ 152,6 142,7 @@
        maxlength="16"
        required
        value="{{post_code or ""}}" />
      {{valid.summary("post_code")}}
    </div>
    <div class="col-md-4 form-group">
      <label for="country">Country</label>


@@ 171,9 162,6 @@
        >{{c.name}}</option>
      {% endfor %}
      </select>
    </div>
    <div class="col-md-8 offset-md-2">
      {{valid.summary("post_code")}}
      {{valid.summary("country")}}
    </div>
    <div class="col-md-4 offset-md-2 form-group">


@@ 186,6 174,7 @@
        placeholder="+1 (123) 123-1234"
        maxlength="32"
        value="{{phone or ""}}" />
      {{valid.summary("phone")}}
    </div>
    <div class="col-md-4 form-group">
      <label for="fax">Fax number</label>


@@ 197,13 186,19 @@
        placeholder="+1 (123) 123-1234 (optional)"
        maxlength="32"
        value="{{fax or ""}}" />
      {{valid.summary("fax")}}
    </div>
    <div class="col-md-8 offset-md-2">
      {{valid.summary("phone_number")}}
      {{valid.summary("fax_number")}}
      <label for="notes">Notes</label>
      <textarea
        id="notes"
        name="notes"
        class="form-control {{valid.cls("notes")}}"
        placeholder="Notes (for your own internal use)"
        rows="4">{{notes or ""}}</textarea>
    </div>
  </div>
  <div class="row">
  <div class="row" style="margin-top: 1rem">
    <details class="extra-details col-md-8 offset-md-2">
      <summary>
        Add optional information (required for some top-level domains)

M names/types/contact.py => names/types/contact.py +1 -0
@@ 5,6 5,7 @@ from srht.database import Base
class DomainContact(Base):
    __tablename__ = 'domain_contact'
    id = sa.Column(sa.Integer, primary_key=True)
    uuid = sa.Column(sa.Unicode, nullable=False)
    created = sa.Column(sa.DateTime, nullable=False)
    updated = sa.Column(sa.DateTime, nullable=False)
    user_id = sa.Column(sa.Integer, sa.ForeignKey('user.id'), nullable=False)