Changeset 405 in main


Ignore:
Timestamp:
10/18/11 16:18:58 (8 years ago)
Author:
pcosquer
Message:

refactor csvimport and add stuff to import a BOM from a csv

Location:
trunk/openPLM
Files:
8 edited

Legend:

Unmodified
Added
Removed
  • trunk/openPLM/plmapp/csvimport.py

    r403 r405  
    1212from openPLM.plmapp.controllers.plmobject import PLMObjectController 
    1313 
    14 HEADERS_SET = set().union(*(cls.get_creation_fields() 
    15         for cls in models.get_all_plmobjects().itervalues())) 
    16 HEADERS = sorted(HEADERS_SET) 
    1714 
    18 _to_underscore = partial(re.compile(r"[\s-]+").sub, "_") 
    1915 
    20 def guess_headers(csv_headers): 
    21     headers = [] 
    22     for header in csv_headers: 
    23         h = _to_underscore(header.lower()) 
    24         if h in HEADERS_SET: 
    25             headers.append(h) 
    26         else: 
    27             headers.append(None) 
    28     return headers 
    29  
    30 class CSVPreview(object): 
    31  
    32     def __init__(self, csv_file, encoding="utf-8"): 
    33         reader = UnicodeReader(csv_file, encoding=encoding) 
    34         self.headers = reader.next() 
    35         self.guessed_headers = guess_headers(self.headers) 
    36         self.rows = tuple(islice(reader, 2)) 
     16_to_underscore = partial(re.compile(r"\s+").sub, "_") 
    3717 
    3818class CSVImportError(StandardError): 
     
    4525        return "CSVImportError:\n\t" + details  
    4626 
    47 REQUIRED_FIELDS = ("type", "reference", "revision", "name", "group", "lifecycle") 
    48 REQUIRED_FIELDS_STR = ", ".join(REQUIRED_FIELDS) 
    49 MISSING_HEARDERS_MSG = "Missing headers: %s are required." % REQUIRED_FIELDS_STR 
     27class Preview(object): 
    5028 
    51 @transaction.commit_on_success 
    52 def import_csv(csv_file, headers, user, encoding="utf-8"): 
    53     from openPLM.plmapp.forms import get_creation_form 
    54     reader = UnicodeReader(csv_file, encoding=encoding) 
    55     headers_dict = dict((h, i) for i, h in enumerate(headers)) 
    56     # checks that required columns are presents 
    57     for field in REQUIRED_FIELDS: 
    58         if field not in headers_dict: 
    59             raise CSVImportError({1: MISSING_HEARDERS_MSG}) 
    60     # read the header 
    61     reader.next() 
    62     errors = defaultdict(ErrorList) 
    63     objects = [] 
    64     # parse each row 
    65     for line, row in enumerate(reader): 
    66         try: 
    67             type_ = row[headers_dict["type"]] 
    68             reference = row[headers_dict["reference"]] 
    69             revision = row[headers_dict["revision"]] 
    70             cls = models.get_all_plmobjects()[type_] 
    71             group = models.GroupInfo.objects.get(name=row[headers_dict["group"]]) 
    72             lifecycle = models.Lifecycle.objects.get(name=row[headers_dict["lifecycle"]]) 
    73             form = get_creation_form(user, cls) 
    74             data = { 
    75                     "type" :type_, 
    76                     "group" : str(group.id), 
    77                     "reference" : reference, 
    78                     "revision" : revision, 
    79                     } 
    80             for field in form.fields: 
    81                 if field not in data and field in headers_dict: 
    82                     data[field] = row[headers_dict[field]] 
    83             form = get_creation_form(user, cls, data) 
    84             if not form.is_valid(): 
    85                 items = (mark_safe(u"%s: %s" % item) for item  
    86                         in form.errors.iteritems()) 
    87                 errors[line + 2].extend(items) 
     29    def __init__(self, csv_file, encoding, known_headers): 
     30        reader = UnicodeReader(csv_file, encoding=encoding) 
     31        self.headers = reader.next() 
     32        self.guessed_headers = self.guess_headers(self.headers, 
     33                known_headers) 
     34        self.rows = tuple(islice(reader, 2)) 
     35 
     36    def guess_headers(self, csv_headers, known_headers): 
     37        headers = [] 
     38        for header in csv_headers: 
     39            h = _to_underscore(header.lower()) 
     40            if h in known_headers: 
     41                headers.append(h) 
    8842            else: 
    89                 obj = PLMObjectController.create_from_form(form, user, True) 
    90                 objects.append(obj) 
    91         except Exception, e: 
    92             errors[line + 2].append(unicode(e)) 
    93     if errors: 
    94         raise CSVImportError(errors) 
    95     for obj in objects: 
    96         obj.unblock_mails() 
    97     return objects 
     43                headers.append(None) 
     44        return headers 
    9845 
     46class CSVImporter(object): 
     47    HEADERS_SET = set().union(*(cls.get_creation_fields() 
     48            for cls in models.get_all_plmobjects().itervalues())) 
     49    HEADERS_SET.add(None) 
     50    HEADERS = sorted(HEADERS_SET) 
     51 
     52    REQUIRED_FIELDS = ("type", "reference", "revision", "name", "group", "lifecycle") 
     53     
     54    @classmethod 
     55    def MISSING_HEADERS_MSG(cls): 
     56        fields = ", ".join(cls.REQUIRED_FIELDS) 
     57        return "Missing headers: %s are required." % fields 
     58 
     59    def __init__(self, csv_file, user, encoding="utf-8"): 
     60        self.csv_file = csv_file 
     61        self.user = user 
     62        self.encoding = encoding 
     63 
     64    def get_preview(self): 
     65        self.csv_file.seek(0) 
     66        return Preview(self.csv_file, self.encoding, self.HEADERS_SET) 
     67 
     68    @transaction.commit_on_success 
     69    def import_csv(self, headers): 
     70        self.csv_file.seek(0) 
     71        reader = UnicodeReader(self.csv_file, encoding=self.encoding) 
     72        self.headers_dict = dict((h, i) for i, h in enumerate(headers)) 
     73        # checks that required columns are presents 
     74        for field in self.REQUIRED_FIELDS: 
     75            if field not in self.headers_dict: 
     76                raise CSVImportError({1: self.MISSING_HEADERS_MSG()}) 
     77        # read the header 
     78        reader.next() 
     79        self.errors = defaultdict(ErrorList) 
     80        self.objects = [] 
     81        # parse each row 
     82        for line, row in enumerate(reader): 
     83            try: 
     84                self.parse_row(line, row) 
     85            except Exception, e: 
     86                self.errors[line + 2].append(unicode(e)) 
     87        if self.errors: 
     88            raise CSVImportError(self.errors) 
     89        for obj in self.objects: 
     90            obj.unblock_mails() 
     91        return self.objects 
     92 
     93    def parse_row(self, line, row): 
     94        from openPLM.plmapp.forms import get_creation_form 
     95        type_ = row[self.headers_dict["type"]] 
     96        reference = row[self.headers_dict["reference"]] 
     97        revision = row[self.headers_dict["revision"]] 
     98        cls = models.get_all_plmobjects()[type_] 
     99        group = models.GroupInfo.objects.get(name=row[self.headers_dict["group"]]) 
     100        lifecycle = models.Lifecycle.objects.get(name=row[self.headers_dict["lifecycle"]]) 
     101        form = get_creation_form(self.user, cls) 
     102        data = { 
     103                "type" :type_, 
     104                "group" : str(group.id), 
     105                "reference" : reference, 
     106                "revision" : revision, 
     107                } 
     108        for field in form.fields: 
     109            if field not in data and field in self.headers_dict: 
     110                data[field] = row[self.headers_dict[field]] 
     111        form = get_creation_form(self.user, cls, data) 
     112        if not form.is_valid(): 
     113            items = (mark_safe(u"%s: %s" % item) for item  
     114                    in form.errors.iteritems()) 
     115            self.errors[line + 2].extend(items) 
     116        else: 
     117            obj = PLMObjectController.create_from_form(form, self.user, True) 
     118            self.objects.append(obj) 
     119 
     120 
     121class BOMImporter(CSVImporter): 
     122     
     123    REQUIRED_FIELDS = ("parent-type", "parent-reference", "parent-revision", 
     124                       "child-type", "child-reference", "child-revision", 
     125                       "quantity", "order")  
     126 
     127    HEADERS_SET = set(REQUIRED_FIELDS)  
     128    HEADERS_SET.add(None) 
     129    HEADERS = sorted(HEADERS_SET) 
     130     
     131    def parse_row(self, line, row):  
     132        from openPLM.plmapp.base_views import get_obj 
     133        ptype = row[self.headers_dict["parent-type"]] 
     134        preference = row[self.headers_dict["parent-reference"]] 
     135        prevision = row[self.headers_dict["parent-revision"]] 
     136        parent = get_obj(ptype, preference, prevision, self.user) 
     137 
     138        ctype = row[self.headers_dict["child-type"]] 
     139        creference = row[self.headers_dict["child-reference"]] 
     140        crevision = row[self.headers_dict["child-revision"]] 
     141        child = get_obj(ctype, creference, crevision, self.user) 
     142 
     143        parent.block_mails() 
     144        child.block_mails() 
     145        self.objects.append(parent) 
     146        self.objects.append(child) 
     147 
     148        quantity = float(row[self.headers_dict["quantity"]]) 
     149        order = float(row[self.headers_dict["order"]]) 
     150 
     151        parent.add_child(child, quantity, order) 
     152    
     153 
     154IMPORTERS = {"csv" : CSVImporter, "bom" : BOMImporter } 
     155 
  • trunk/openPLM/plmapp/forms.py

    r403 r405  
    3333from django.utils.translation import ugettext_lazy as _ 
    3434from django.contrib.sites.models import Site 
     35from django.utils.functional import memoize 
    3536 
    3637import openPLM.plmapp.models as m 
     
    465466 
    466467 
    467 class CSVHeaderForm(forms.Form): 
    468     header = forms.TypedChoiceField(choices=zip(csvimport.HEADERS, 
    469         csvimport.HEADERS), required=False) 
    470  
    471  
    472 class BaseHeadersFormset(BaseFormSet): 
    473  
    474     def clean(self): 
    475         if any(self.errors): 
    476             return 
    477         headers = [] 
    478         for form in self.forms: 
    479             header = form.cleaned_data['header'] 
    480             if header and header in headers: 
    481                 raise forms.ValidationError(_("Columns must have distict headers.")) 
    482             headers.append(header)  
    483         for field in csvimport.REQUIRED_FIELDS: 
    484             if field not in headers: 
    485                 raise forms.ValidationError(csvimport.MISSING_HEARDERS_MSG) 
    486         self.headers = headers 
    487  
    488  
    489 HeadersFormset = formset_factory(CSVHeaderForm, extra=0, 
    490         formset=BaseHeadersFormset) 
    491  
     468def get_headers_formset(Importer): 
     469    class CSVHeaderForm(forms.Form): 
     470        header = forms.TypedChoiceField(choices=zip(Importer.HEADERS, 
     471            Importer.HEADERS), required=False) 
     472 
     473    class BaseHeadersFormset(BaseFormSet): 
     474 
     475        def clean(self): 
     476            if any(self.errors): 
     477                return 
     478            headers = [] 
     479            for form in self.forms: 
     480                header = form.cleaned_data['header'] 
     481                if header and header in headers: 
     482                    raise forms.ValidationError(_("Columns must have distinct headers.")) 
     483                headers.append(header)  
     484            for field in Importer.REQUIRED_FIELDS: 
     485                if field not in headers: 
     486                    raise forms.ValidationError(Importer.MISSING_HEADERS_MSG()) 
     487            self.headers = headers 
     488 
     489 
     490    return formset_factory(CSVHeaderForm, extra=0, formset=BaseHeadersFormset) 
     491 
     492get_headers_formset = memoize(get_headers_formset, {}, 1) 
     493 
  • trunk/openPLM/plmapp/tests/csvimport.py

    r403 r405  
    8383        UnicodeWriter(csv_file).writerows(csv_rows) 
    8484        csv_file.seek(0) 
    85         headers = CSVPreview(csv_file).guessed_headers 
    86         csv_file.seek(0) 
    87         objects = import_csv(csv_file, headers, self.user) 
     85        importer = CSVImporter(csv_file, self.user) 
     86        headers = importer.get_preview().guessed_headers 
     87        objects = importer.import_csv(headers) 
    8888        self.assertEquals(len(csv_rows) - 1, len(objects)) 
    8989        sp1 = get_obj("SinglePart", "sp1", "s", self.user) 
     
    102102        UnicodeWriter(csv_file).writerows(csv_rows) 
    103103        csv_file.seek(0) 
    104         headers = CSVPreview(csv_file).guessed_headers 
    105         csv_file.seek(0) 
    106         self.assertRaises(CSVImportError, import_csv, 
    107                 csv_file, headers, self.user) 
     104        importer = CSVImporter(csv_file, self.user) 
     105        headers = importer.get_preview().guessed_headers 
     106        self.assertRaises(CSVImportError, importer.import_csv, headers) 
    108107        self.assertEquals(plmobjects, list(PLMObject.objects.all())) 
    109108 
  • trunk/openPLM/plmapp/views/main.py

    r404 r405  
    11041104    return r2r("groups/refuse_invitation.htm", ctx, request) 
    11051105 
    1106 @handle_errors 
    1107 def import_csv_init(request): 
     1106@handle_errors(undo="../..") 
     1107def import_csv_init(request, target="csv"): 
    11081108    obj, ctx = get_generic_data(request) 
    11091109    if request.method == "POST": 
     
    11181118            tmp.close() 
    11191119            encoding = csv_form.cleaned_data["encoding"] 
    1120             return HttpResponseRedirect("/import/csv/%s/%s/" % (name, encoding)) 
     1120            return HttpResponseRedirect("/import/%s/%s/%s/" % (target, name, 
     1121                                        encoding)) 
    11211122    else: 
    11221123        csv_form = CSVForm() 
    11231124    ctx["csv_form"] = csv_form 
    11241125    ctx["step"] = 1 
     1126    ctx["target"] = target 
    11251127    return r2r("import/csv.htm", ctx, request) 
    11261128 
    1127 @handle_errors 
    1128 def import_csv_apply(request, filename, encoding): 
     1129@handle_errors(undo="../..") 
     1130def import_csv_apply(request, target, filename, encoding): 
    11291131    obj, ctx = get_generic_data(request) 
    11301132    ctx["encoding_error"] = False 
    11311133    ctx["io_error"] = False 
     1134    Importer = csvimport.IMPORTERS[target] 
     1135    Formset = forms.get_headers_formset(Importer) 
    11321136    try: 
    11331137        path = os.path.join(tempfile.gettempdir(), 
    11341138                            "openplmcsv" + request.user.username + filename) 
    11351139        with open(path, "rb") as csv_file: 
    1136             preview = csvimport.CSVPreview(csv_file, encoding) 
     1140            importer = Importer(csv_file, request.user, encoding) 
     1141            preview = importer.get_preview() 
    11371142        if request.method == "POST": 
    1138             headers_formset = forms.HeadersFormset(request.POST) 
     1143            headers_formset = Formset(request.POST) 
    11391144            if headers_formset.is_valid(): 
    11401145                headers = headers_formset.headers 
    11411146                try: 
    11421147                    with open(path, "rb") as csv_file: 
    1143                         csvimport.import_csv(csv_file, headers, request.user, 
    1144                                              encoding) 
     1148                        importer = Importer(csv_file, request.user, encoding) 
     1149                        importer.import_csv(headers) 
    11451150                except csvimport.CSVImportError as exc: 
    11461151                    ctx["errors"] = exc.errors.iteritems() 
     
    11501155        else: 
    11511156            initial = [{"header": header} for header in preview.guessed_headers] 
    1152             headers_formset = forms.HeadersFormset(initial=initial) 
     1157            headers_formset = Formset(initial=initial) 
    11531158        ctx.update({ 
    11541159            "preview" :  preview, 
     
    11651170    ctx["csv_form"] = CSVForm(initial={"encoding" : encoding}) 
    11661171    ctx["step"] = 2 
     1172    ctx["target"] = target 
    11671173    return r2r("import/csv.htm", ctx, request) 
    11681174 
  • trunk/openPLM/templates/DisplayHomePage.htm

    r403 r405  
    125125                </a> 
    126126             </li> 
     127            <li class="ui-button ui-button-text  ui-button-text-only ui-widget ui-state-default ui-corner-all"  id="ImportBOMButton"> 
     128                <a href="/import/bom/"> 
     129                    {% trans "Import a BOM" %} 
     130                </a> 
     131             </li> 
    127132        </ul> 
    128133    </form> 
  • trunk/openPLM/templates/import/csv.htm

    r403 r405  
    55{% block content %} 
    66    <h3>{% trans "Import of a csv file" %}</h3> 
    7     {% with "/import/csv/" as action %} 
     7    {% with "/import/"|add:target|add:"/" as action %} 
    88        {% with csv_form as form %} 
    99            {% include "snippets/undo_form.htm" %} 
  • trunk/openPLM/templates/import/done.htm

    r403 r405  
    77    <div> 
    88        <a href="/import/csv/">{% trans "Click here to import a new CSV file." %}</a> 
     9        <a href="/import/bom/">{% trans "Click here to import a new BOM." %}</a> 
    910    </div> 
    1011{% endblock %} 
  • trunk/openPLM/urls.py

    r403 r405  
    3636import openPLM.plmapp.views.api as api 
    3737from django.contrib.auth.views import login, logout 
     38from openPLM.plmapp.csvimport import IMPORTERS 
    3839 
    3940from django.contrib import admin 
     
    6768group_dict = {'obj_type':'Group', 'obj_revi':'-'} 
    6869 
     70import_url = r'^import/(?P<target>%s)/' % ("|".join(IMPORTERS.keys())) 
     71 
    6972urlpatterns += patterns('', 
    7073    (r'^admin/', include(admin.site.urls)), 
     
    7780    (r'^object/create/$', create_object), 
    7881    (r'^comments/', include('django.contrib.comments.urls')), 
    79     ('^import/csv/$', import_csv_init), 
    80     ('^import/csv/(?P<filename>[\w]+)/(?P<encoding>[\w]+)/$', import_csv_apply), 
     82    (import_url + '$', import_csv_init), 
     83    (import_url + '(?P<filename>[\w]+)/(?P<encoding>[\w]+)/$', 
     84        import_csv_apply), 
    8185    ('^import/done/$', import_csv_done), 
    8286    ) 
Note: See TracChangeset for help on using the changeset viewer.