def gdpr_consent(shop_url, referringpage): # Create a unique encrypted ID for this removal to write in Audit Log and address2 line removal_id_str = datetime.datetime.now().strftime('%B-%d-%Y %H:%M:%S') + 'consent' dataevent_id = encrypt_ids(removal_id_str) # ####################################################### # Log Data Removal in Audit Table (All times are in UTC) # ####################################################### current_datetime = datetime.datetime.now() action = 'Data Consent' method = 'Consent Form "Accept Button"' comment = 'Data subject granted consent to data collection for the purpose of marketing.' status = 'Completed' legal_basis = 'Preparing or Performing a Contract (Product Sales) and Consent (Granted for Marketing)' purpose = 'Ecommerce - Processing Customer Purchases and Marketing' customer_id= 'Unregistered Customer' # Create AuditLog Instance AuditLogInst = DataEventLogger(shop_url) # Add Consent Granted Entry line AuditLogInst.insert_data_processing_event(current_datetime, action, method, referringpage, comment,\ customer_id, purpose, status, legal_basis)
def gdpr_removal_send_email(removal_request_email, shop_url, referringpage, country): request_date = datetime.date.today().strftime('%B %d %Y') # Create and Activate session for API calls ShopDeatzInst = ShopDeatz.objects.get(shop_url=shop_url) session = shopify.Session(shop_url, ShopDeatzInst.auth_token) shopify.ShopifyResource.activate_session(session) # Gets an object that contains all the shop info eqvalent to GET endpoint /admin/shop.json shop_info = shopify.Shop.current() # Make customer instance to check if customer email exists customer deatails (returns a list so use the first one) search_query = 'email:' + str(removal_request_email) CustomerInst = shopify.Customer.search(q=search_query) if len(CustomerInst) <= 0: # ############################################################## # Log INVALID Data Removal in Audit Table (All times are in UTC) # ############################################################## current_datetime = datetime.datetime.now() action = 'Invalid Data Removal Request' method = 'Data Removal Form' comment = 'Email does not correspond to any record in the database. No data removed.' customer_id = 'Unregistered Customer' marketing_consent = '-' status = 'Completed' legal_basis = '-' purpose = '-' # Create AuditLog Instance AuditLogInst = DataEventLogger(shop_url) # Add Data Removal Entry line AuditLogInst.insert_data_processing_event(current_datetime, action, method, referringpage, comment,\ customer_id, purpose, status, legal_basis) return 0 else: pass # Generate 101 character random string confirmation code confirmation_code = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(101)) # Check if removal_email exists in RemovalConfirmationCodes in the database for this user # (because user might have clciked multiple times on the delete link by mistake which would generate new entries in the DB) # if it exists then just change the confirmation code, otherwise write a new entry for the user email try: ConfirmationCodesInst = RemovalConfirmationCodes.objects.get(removal_request_email=removal_request_email) ConfirmationCodesInst.confirmation_code = confirmation_code ConfirmationCodesInst.save() except ObjectDoesNotExist: ConfirmationCodesInst = RemovalConfirmationCodes() ConfirmationCodesInst.confirmation_code = confirmation_code ConfirmationCodesInst.removal_request_email = removal_request_email ConfirmationCodesInst.save() removal_link = "https://cdn1.gdprinsider.ovh/dataevent/?" + "requesttype=confirmremoval" + "&shopurl=" + shop_url \ + "&email=" + removal_request_email + "&country=" + country + "&confirm=" + confirmation_code SENDER = str(shop_info.name) + "<*****@*****.**>" # Replace [email protected] with a "To" address. If your account RECIPIENT = str(removal_request_email) # If necessary, replace us-west-2 with the AWS Region you're using for Amazon SES. AWS_REGION = "eu-west-1" # The email body for recipients with non-HTML email clients. BODY_TEXT = ( "Hi there,\r\n" "In response to your subject data removal request received on %s, please click the following link to " "complete your data removal process.\r\n" "%s \r\n" "Please note that your data will be purged from our store's system and that we will no longer have any trace of " "your customer details and purchase history. As such, you will no longer be entitled to any refunds or product exchanges.\r\n" "Thank you for your inquiry.\r\n" "Regards,\r\n" "%s" ) %( request_date, removal_link, str(shop_info.name) ) # print BODY_TEXT ############################################### # The HTML body of the email. ############################################### # FRENCH EMAIL TEMPLATE if country == 'France': # The subject line for the email. SUBJECT = str(shop_info.name) + " - GDPR Demande de Suppression de Données" # Create French format request_date request_date = datetime.date.today().strftime('%d/%m/%Y') # Get content of html template template_relative_location = 'templates/emailtemplates/data_del_FR.html' filelocation = os.path.join(settings.BASE_DIR, template_relative_location) BODY_HTML = "" for line in open(filelocation): new_line = unicode(line.decode("utf-8")) new_line = new_line.rstrip('\n') # print new_line BODY_HTML = BODY_HTML + new_line # ALL OTHER (ENGLISH EMAIL TEMPLATE) else: # The subject line for the email. SUBJECT = str(shop_info.name) + " - GDPR Data Removal Request" # Get content of html template template_relative_location = 'templates/emailtemplates/data_del.html' filelocation = os.path.join(settings.BASE_DIR, template_relative_location) BODY_HTML = "" for line in open(filelocation): new_line = line.rstrip('\n') # print new_line BODY_HTML = BODY_HTML + new_line # Replace content BODY_HTML = BODY_HTML.replace('#STORENAME#', str(shop_info.name)) BODY_HTML = BODY_HTML.replace('#REQUESTDATE#', request_date) BODY_HTML = BODY_HTML.replace('#DATAREMOVELINK#', removal_link) # print '\n' # print '#####################################################################' # print BODY_HTML # The character encoding for the email. CHARSET = "UTF-8" ########################################################### # EMAIL SENDING CODE BLOCK ########################################################### # Try to send the email. try: # Select the email sending service to use SendGrid or Amazon depending on value of settings.ACTIVE_TRANSACTIONALEMAIL_SERVICE if settings.ACTIVE_TRANSACTIONALEMAIL_SERVICE == 'AMAZON': # Create a new SES resource and specify a region. client = boto3.client( 'ses', region_name=AWS_REGION, aws_access_key_id=settings.aws_id, aws_secret_access_key=settings.aws_secret ) #Provide the contents of the email. response = client.send_email( Destination={ 'ToAddresses': [ RECIPIENT, ], }, Message={ 'Body': { 'Html': { 'Charset': CHARSET, 'Data': BODY_HTML, }, 'Text': { 'Charset': CHARSET, 'Data': BODY_TEXT, }, }, 'Subject': { 'Charset': CHARSET, 'Data': SUBJECT, }, }, Source=SENDER, # If you are not using a configuration set, comment or delete the # following line #ConfigurationSetName=CONFIGURATION_SET, ) elif settings.ACTIVE_TRANSACTIONALEMAIL_SERVICE == 'SENDGRID': sg = sendgrid.SendGridAPIClient(apikey=settings.SENDGRID_API_KEY) from_email = Email(email="*****@*****.**", name=str(shop_info.name)) to_email = Email(RECIPIENT) subject = SUBJECT content = Content("text/html", BODY_HTML) mail = Mail(from_email, subject, to_email, content) response = sg.client.mail.send.post(request_body=mail.get()) print(response.status_code) print(response.body) print(response.headers) # Display an error if something goes wrong. except ClientError as e: print ("ERROR - Failed to send email to %s") %(RECIPIENT) print(e.response.status_code) else: print("Email sent! Message ID:") print(response.status_code) # ####################################################### # Log Data Removal in Audit Table (All times are in UTC) # ####################################################### current_datetime = datetime.datetime.now() action = 'Data Removal Request' method = 'Data Removal Form' comment = 'Confirmation link sent via email to the address on record.' customer_id = CustomerInst[0].id marketing_consent = CustomerInst[0].accepts_marketing status = 'Completed' if marketing_consent is True: legal_basis = 'Preparing or Performing a Contract (Product Sales) and Consent (Granted for Marketing)' purpose = 'Ecommerce - Processing Customer Purchases and Marketing' else: legal_basis = 'Preparing or Performing a Contract (Product Sales)' purpose = 'Ecommerce - Processing Customer Purchases' # Create AuditLog Instance AuditLogInst = DataEventLogger(shop_url) # Add Data Removal Entry line AuditLogInst.insert_data_processing_event(current_datetime, action, method, referringpage, comment,\ customer_id, purpose, status, legal_basis) return 0
def gdpr_copy(copy_request_email, shop_url, referringpage, country): request_date = datetime.date.today().strftime('%B %d %Y') # Create and Activate session for API calls ShopDeatzInst = ShopDeatz.objects.get(shop_url=shop_url) session = shopify.Session(shop_url, ShopDeatzInst.auth_token) shopify.ShopifyResource.activate_session(session) # Gets an object that contains all the shop info eqvalent to GET endpoint /admin/shop.json shop_info = shopify.Shop.current() # Make customer instance to extract customer deatails (returns a list so use the first one) # .find() will not work on Customers using params, Search queries are only available on the Customer resource as a separate .search() method # This is because to search, you need to access a different endpont not /admin/customers.json but rather /admin/customers/search.json search_query = 'email:' + str(copy_request_email) CustomerInst = shopify.Customer.search(q=search_query) if len(CustomerInst) <= 0: # ############################################################## # Log INVALID Data Access in Audit Table (All times are in UTC) # ############################################################## current_datetime = datetime.datetime.now() action = 'Invalid Data Access Request' method = 'Data Access Form' comment = 'Email does not correspond to any record in the database. No data transmitted.' customer_id = 'Unregistered Customer' marketing_consent = '-' status = 'Completed' legal_basis = '-' purpose = '-' # Create AuditLog Instance AuditLogInst = DataEventLogger(shop_url) # Add Data Removal Entry line AuditLogInst.insert_data_processing_event(current_datetime, action, method, referringpage, comment,\ customer_id, purpose, status, legal_basis) return 0 else: pass SENDER = str(shop_info.name) + "<*****@*****.**>" # Replace [email protected] with a "To" address. If your account RECIPIENT = str(copy_request_email) # If necessary, replace us-west-2 with the AWS Region you're using for Amazon SES. AWS_REGION = "eu-west-1" # The email body for recipients with non-HTML email clients. BODY_TEXT = ("Hi there,\r\n" "In response to your subject data access request received on %s, " "please find below the requested copy of your personal data.\r\n" '---------------------------------------------\r\n' 'Email: %s\r\n' 'First Name: %s\r\n' 'Last Name: %s\r\n' 'Phone: %s\r\n' 'Company: %s\r\n' 'Address: %s\r\n' 'Address: %s\r\n' 'City: %s\r\n' 'Province: %s\r\n' 'Country: %s\r\n' '---------------------------------------------\r\n' "As per European General Data Protection Regulation, you may rectify this data at any time by " "loging in to your shop account. Alternatively, you can request data removal by clicking the data " "icon at the bottom of the store's pages.\r\n" "Thank you for your inquiry.\r\n" "Regards,\r\n" "%s" ) %( request_date, str(CustomerInst[0].email), str(CustomerInst[0].first_name), str(CustomerInst[0].last_name), str(CustomerInst[0].phone), str(CustomerInst[0].addresses[0].company), str(CustomerInst[0].addresses[0].address1), str(CustomerInst[0].addresses[0].address2), str(CustomerInst[0].addresses[0].city), str(CustomerInst[0].addresses[0].province), str(CustomerInst[0].addresses[0].country), str(shop_info.name) ) # print BODY_TEXT ############################################# # The HTML body of the email. ############################################# # FRENCH EMAIL TEMPLATE if country == 'France': # The subject line for the email. SUBJECT = str(shop_info.name) + " - GDPR Demande de Copie de Données" # Create French format request_date request_date = datetime.date.today().strftime('%d/%m/%Y') # Get content of html template template_relative_location = 'templates/emailtemplates/data_copy_FR.html' filelocation = os.path.join(settings.BASE_DIR, template_relative_location) BODY_HTML = "" for line in open(filelocation): new_line = unicode(line.decode("utf-8")) new_line = new_line.rstrip('\n') # print unicodedata.normalize('NFKD', new_line).encode('utf-8','ignore') BODY_HTML = BODY_HTML + new_line # ALL OTHERS (ENGLISH EMAIL TEMPLATE) else: # The subject line for the email. SUBJECT = str(shop_info.name) + " - GDPR Data Access Request" template_relative_location = 'templates/emailtemplates/data_copy.html' filelocation = os.path.join(settings.BASE_DIR, template_relative_location) BODY_HTML = "" for line in open(filelocation): new_line = line.rstrip('\n') BODY_HTML = BODY_HTML + new_line # replace content of template BODY_HTML = BODY_HTML.replace('#STORENAME#', str(shop_info.name)) BODY_HTML = BODY_HTML.replace('#REQUESTDATE#', request_date) BODY_HTML = BODY_HTML.replace('#EMAIL#', str(CustomerInst[0].email)) BODY_HTML = BODY_HTML.replace('#FNAME#', str(CustomerInst[0].first_name)) BODY_HTML = BODY_HTML.replace('#LNAME#', str(CustomerInst[0].last_name)) BODY_HTML = BODY_HTML.replace('#PHONE#', str(CustomerInst[0].phone)) BODY_HTML = BODY_HTML.replace('#COMPANY#', str(CustomerInst[0].addresses[0].company)) BODY_HTML = BODY_HTML.replace('#ADDRESS1#', str(CustomerInst[0].addresses[0].address1)) BODY_HTML = BODY_HTML.replace('#ADDRESS2#', str(CustomerInst[0].addresses[0].address2)) BODY_HTML = BODY_HTML.replace('#CITY#', str(CustomerInst[0].addresses[0].city)) BODY_HTML = BODY_HTML.replace('#PROVINCE#', str(CustomerInst[0].addresses[0].province)) BODY_HTML = BODY_HTML.replace('#COUNTRY#', str(CustomerInst[0].addresses[0].country)) BODY_HTML = BODY_HTML.replace('#ZIP#', str(CustomerInst[0].addresses[0].zip)) # print '\n' # print '#####################################################################' # print BODY_HTML # The character encoding for the email. CHARSET = "UTF-8" ########################################################### # EMAIL SENDING CODE BLOCK ########################################################### # Try to send the email. try: # Select the email sending service to use SendGrid or Amazon depending on value of settings.ACTIVE_TRANSACTIONALEMAIL_SERVICE if settings.ACTIVE_TRANSACTIONALEMAIL_SERVICE == 'AMAZON': # Create a new SES resource and specify a region. client = boto3.client( 'ses', region_name=AWS_REGION, aws_access_key_id=settings.aws_id, aws_secret_access_key=settings.aws_secret ) #Provide the contents of the email. response = client.send_email( Destination={ 'ToAddresses': [ RECIPIENT, ], }, Message={ 'Body': { 'Html': { 'Charset': CHARSET, 'Data': BODY_HTML, }, 'Text': { 'Charset': CHARSET, 'Data': BODY_TEXT, }, }, 'Subject': { 'Charset': CHARSET, 'Data': SUBJECT, }, }, Source=SENDER, # If you are not using a configuration set, comment or delete the # following line #ConfigurationSetName=CONFIGURATION_SET, ) elif settings.ACTIVE_TRANSACTIONALEMAIL_SERVICE == 'SENDGRID': sg = sendgrid.SendGridAPIClient(apikey=settings.SENDGRID_API_KEY) from_email = Email(email="*****@*****.**", name=str(shop_info.name)) to_email = Email(RECIPIENT) subject = SUBJECT content = Content("text/html", BODY_HTML) mail = Mail(from_email, subject, to_email, content) response = sg.client.mail.send.post(request_body=mail.get()) print(response.status_code) print(response.body) print(response.headers) # Display an error if something goes wrong. except ClientError as e: print ("ERROR - Failed to send email to %s") %(RECIPIENT) print(e.response.status_code) else: print("Email sent! Message ID:"), print(response.status_code) # ####################################################### # Log Data Access in Audit Table (All times are in UTC) # ####################################################### current_datetime = datetime.datetime.now() action = 'Data Access Request' method = 'Data Access Form' comment = 'Requested data transmitted via email to the address on record.' customer_id = CustomerInst[0].id marketing_consent = CustomerInst[0].accepts_marketing status = 'Completed' if marketing_consent is True: legal_basis = 'Preparing or Performing a Contract (Product Sales) and Consent (Granted for Marketing)' purpose = 'Ecommerce - Processing Customer Purchases and Marketing' else: legal_basis = 'Preparing or Performing a Contract (Product Sales)' purpose = 'Ecommerce - Processing Customer Purchases' # Create AuditLog Instance AuditLogInst = DataEventLogger(shop_url) # Add Data Removal Entry line AuditLogInst.insert_data_processing_event(current_datetime, action, method, referringpage, comment,\ customer_id, purpose, status, legal_basis) return 0
def activateRecurringCharge(request): print '==> activateRecurringCharge()' # Use the charge id that is passed in the GET request to create an instance of your store's data charge_id = request.GET.get('charge_id') # Create and activate session # Add try / Except statement here for double return_url call # On the second call ShopDeatz object will have been deleted and will return ObjectNotFound at that point just return the decline_page.html try: ShopDeatzInst = ShopDeatz.objects.get(charge_id=charge_id) shop_url = ShopDeatzInst.shop_url auth_token = ShopDeatzInst.auth_token except ObjectDoesNotExist: print 'rendering from except' # Return App Charge Declined Page shop_url = request.GET.get('shop', False) #full_shop_url = 'https://' + shop_url context = { 'SHOPIFY_API_KEY': settings.SHOPIFY_API_KEY, #'shop_url': full_shop_url, #'apps_link': (full_shop_url + '/admin/apps/') } print context return render(request, 'installer/decline_page.html', context) session = shopify.Session(shop_url, ShopDeatzInst.auth_token) shopify.ShopifyResource.activate_session(session) print 'Activationg charge for %s' %(shop_url) # use the charge_id to find the correct RecurringApplicationCharge within the store's charges charge = shopify.RecurringApplicationCharge.find(charge_id) # Check the status of the application charge # if it is declined then remove the store from our DB to restrict access print "Current charge status = %s" %(charge.status) if charge.status != 'accepted' and charge.status != 'active': print 'Declined Deleting Shop from DB' ShopDeatzInst.delete() # Return App Charge Declined Page full_shop_url = 'https://' + shop_url context = { 'SHOPIFY_API_KEY': settings.SHOPIFY_API_KEY, 'shop_url': full_shop_url, 'apps_link': (full_shop_url + '/admin/apps/') } print context return render(request, 'installer/decline_page.html', context) elif charge.status == 'active': # redirect to dashboard return dashboard.views.index(request) # Otherwise if merchant accepted then, activate the charge and finalize the installation else: # Activate the charge charge.activate() final_status = charge.status print 'Final charge status %s' %(final_status) print 'Finalizing Installation' # ############################# # Finish installation # ############################# # Install Script Tag scripttag_return = createScriptTag() print scripttag_return # Create uinstall webhook (removed during testing phase) webhook_return = createUninstallWebHook() print webhook_return # ############################### # CREATE REQUIRED MODELS # ############################### # Note: Wrap all model creations in try/except statements to make check that you are not adding an entry that already exists # double entries bug out the entire code when doing lookups. No need to wrap the ShopDeatz, there is already a try/except check above # Save the same info in models.InstallTracker # if an entry for the store exists then update the info of the entry try: InstallTrackerInst = InstallTracker.objects.get(shop_url=shop_url) install_datetime = datetime.datetime.now() InstallTrackerInst.auth_token = auth_token InstallTrackerInst.install_date = install_datetime InstallTrackerInst.db_purge = '-' InstallTrackerInst.save() # otherwise if no entry exists then create one except ObjectDoesNotExist: InstallTrackerInst = InstallTracker() install_datetime = datetime.datetime.now() InstallTrackerInst.shop_url = shop_url InstallTrackerInst.auth_token = auth_token InstallTrackerInst.install_date = install_datetime InstallTrackerInst.db_purge = '-' InstallTrackerInst.save() print 'Install written in models.InstallTracker' # Create Customization Model entry for shop try: ShopCustomInst = ShopCustomizations.objects.get(shop_url=shop_url) except ObjectDoesNotExist: DefaultTemplateInst = TemplateDefaults.objects.get(template_name='retro') ShopCustInst = ShopCustomizations() ShopCustInst.shop_url = shop_url ShopCustInst.active_template = DefaultTemplateInst.template_name ShopCustInst.background_color = DefaultTemplateInst.background_color ShopCustInst.text_color = DefaultTemplateInst.text_color ShopCustInst.secondary_text_color = DefaultTemplateInst.secondary_text_color ShopCustInst.active_tab_text_color = DefaultTemplateInst.active_tab_text_color ShopCustInst.accept_color = DefaultTemplateInst.accept_color ShopCustInst.decline_color = DefaultTemplateInst.decline_color ShopCustInst.font_type = DefaultTemplateInst.font_type ShopCustInst.font_size = DefaultTemplateInst.font_size ShopCustInst.time_delay= DefaultTemplateInst.time_delay ShopCustInst.save() # Create Shop Settings Model entry for shop try: ShopSettingsInst = ShopSettings.objects.get(shop_url=shop_url) except ObjectDoesNotExist: ShopSettingsInst = ShopSettings() ShopSettingsInst.shop_url = shop_url ShopSettingsInst.save() # Create Shop Marketing Services entry for shop try: ShopMarketingServicesInst = ShopMarketingServices.objects.get(shop_url=shop_url) except ObjectDoesNotExist: ShopMarketingServicesInst = ShopMarketingServices() ShopMarketingServicesInst.shop_url = shop_url ShopMarketingServicesInst.save() # Create Shop Advanced Settings entry for shop try: AdvancedSettingsInst = AdvancedSettings.objects.get(shop_url=shop_url) except ObjectDoesNotExist: AdvancedSettingsInst = AdvancedSettings() AdvancedSettingsInst.shop_url = shop_url AdvancedSettingsInst.save() # Create Email Service Provider entry in DB for shop try: ESPInst = ESPCredentials.objects.get(shop_url=shop_url) except ObjectDoesNotExist: ESPInst = ESPCredentials() ESPInst.shop_url = shop_url ESPInst.save() ##################################################################### # MYSQL SETUP ##################################################################### # Check if table already exists in case of re-installs and abandonned upgrades. In these cases a table will already exist and there # is no need to re-create one or to launch initial_data_import, doing so would re-write the customers data or append the same data again AuditLogInst = DataEventLogger(shop_url) table_exists = AuditLogInst.check_table_exists() if table_exists == False: ##################################################### # Create MYSQL table Audit Log (Do NOT wrap in try) #################################################### AuditLogInst.create_shop_audit_table() ##################################################### # Queue Asynchronous Initial Data Import #################################################### # This calls the code that we wish to run asynchronously. # Basically once the app finishes installing in finalize, we want to immediately let him continue on his way, # while we process imports in the all in the background. The function called below will be sent to the rabbitmq queue and # will wait there for a worker to pick it up and execute the code in the background asynchronously. That we this finalize() # won't have to wait for this process to finish before rendering the finalize page. We can render the user backend and allow # the impot function to finish in its own time. Without locking the flow control. initial_data_import.delay(shop_url, auth_token) else: ShopDeatzInst.initial_import = True ShopDeatzInst.last_import_datetime = datetime.datetime.now() ShopDeatzInst.save() print '%s SQL table exists, table creation and initial_data_import have been skipped' %(shop_url) pass # You must pass 2 arguements to the EASDK JS, your app's api_key and the FULL path to the store that is installing the app. full_shop_url = 'https://' + shop_url context = { 'SHOPIFY_API_KEY': settings.SHOPIFY_API_KEY, 'shop_url': full_shop_url } print 'APP INSTALLATION SUCCESSFULLY TERMINATED' # Now render the emmbeded home.html template using the context above return render(request, 'dashboard/home.html', context)
def removal_queue_processing(): print 'started task' current_datetime = datetime.datetime.now() # Create an instance of RemovalQueue and loop through all entries in the queue RemovalQueue_list = RemovalQueue.objects.all() for RemovalQueue_entry in RemovalQueue_list: print RemovalQueue_entry # Get the Shop's 0auth token, that was stored in installer.models.ShopDeatz during installation ShopDeatzInst = ShopDeatz.objects.get( shop_url=RemovalQueue_entry.shop_url) auth_token = ShopDeatzInst.auth_token # Create a session object using shop_url and 0auth_token session = shopify.Session(RemovalQueue_entry.shop_url, auth_token) # Activate the Session shopify.ShopifyResource.activate_session(session) # STEP1. Create an instance of the customer by looking him up using his ID customer = shopify.Customer.find(RemovalQueue_entry.customer_id) # STEP1. Check if last_order_id has not changed print customer.last_order_id print RemovalQueue_entry.last_order_id if str(customer.last_order_id) == RemovalQueue_entry.last_order_id: print "last order ids match" pass # In case the customer has initiated a new order in between his removal request date and now, then re-set his last_order_date to # account for the new chargeback period else: print "orders don't match" last_order = shopify.Order.find(customer.last_order_id) last_order_date = dateutil.parser.parse(last_order.created_at) # This will update all entries corresponding to the the customer_id and shop url. This is used in case the customer would have # submitted multiple removal queries RemovalQueue.objects.filter( customer_id=RemovalQueue_entry.customer_id, shop_url=RemovalQueue_entry.shop_url).update( last_order_id=customer.last_order_id) RemovalQueue.objects.filter( customer_id=RemovalQueue_entry.customer_id, shop_url=RemovalQueue_entry.shop_url).update( last_order_date=last_order_date) continue ################################################################################################ # Shopify Removal Processing ############################################################################################### # The Rest of this code will not execute unless the if statement above is successfully passed # Charge Back Period (defined in number of days, should be set to 181 days) chargeback_period = 6 # calculate how many days have passed since last order days_since_last_order = current_datetime - RemovalQueue_entry.last_order_date.replace( tzinfo=None) print 'days since last order' print days_since_last_order # if less than 181 days don't do anything move to the next removal entry in the queue if days_since_last_order < datetime.timedelta(chargeback_period): print "ChargeBack period hasn't been terminated for customer_id %s" % ( str(RemovalQueue_entry.customer_id)) continue elif days_since_last_order >= datetime.timedelta(chargeback_period): print "ChargeBack period terminated sending removal email to Shopify Privacy Team" # ############################################################## # Use Sendgrid to send removal request to [email protected] # ############################################################## # Get Shop Owner's email Address for CC'ing him in the email ShopInfo = shopify.Shop.current() owner_email = ShopInfo.email owner_name = ShopInfo.shop_owner shop_name = ShopInfo.name shop_id = ShopInfo.id template_relative_location = 'templates/emailtemplates/data_removal.html' filelocation = os.path.join(settings.BASE_DIR, template_relative_location) BODY_HTML = "" for line in open(filelocation): new_line = line.rstrip('\n') BODY_HTML = BODY_HTML + new_line # replace content of template BODY_HTML = BODY_HTML.replace('#SHOPNAME#', str(shop_name)) BODY_HTML = BODY_HTML.replace('#SHOPID#', str(shop_id)) BODY_HTML = BODY_HTML.replace('#CUSTOMERID#', str(RemovalQueue_entry.customer_id)) BODY_HTML = BODY_HTML.replace( '#LASTORDERID#', str(RemovalQueue_entry.last_order_id)) BODY_HTML = BODY_HTML.replace( '#DOLO#', RemovalQueue_entry.last_order_date.strftime("%d %B %Y")) BODY_HTML = BODY_HTML.replace( '#EOCBP#', ((RemovalQueue_entry.last_order_date + datetime.timedelta(180)).strftime("%d %B %Y"))) BODY_HTML = BODY_HTML.replace( '#DOGDPR#', RemovalQueue_entry.removal_request_date.strftime("%d %B %Y")) BODY_HTML = BODY_HTML.replace('#STOREOWNER#', str(owner_name)) # The character encoding for the email. CHARSET = "UTF-8" # SendGrid Code Block RECIPIENT = '*****@*****.**' # This should be changed to the email of the Shopify Privacy Team SUBJECT = str(shop_name) + " - GDPR Data Removal Request" #from_email = Email(email="*****@*****.**", name=shop_name) #subject = SUBJECT #to_email = Email(RECIPIENT) #content = Content("text/html", BODY_HTML) #mail = Mail(from_email, subject, to_email, content) #mail.personalizations[0].add_to(Email(RECIPIENT)) # This should be the Shopify Privacy team [email protected] #mail.personalizations[0].add_cc(Email(owner_email)) ## CC store owner print "owner email %s" % (owner_email) sg = sendgrid.SendGridAPIClient(apikey=settings.SENDGRID_API_KEY) from_email = Email( email="*****@*****.**", name=str(shop_name)) to_email = Email(RECIPIENT) subject = SUBJECT content = Content("text/html", BODY_HTML) mail = Mail(from_email, subject, to_email, content) mail.personalizations[0].add_cc(Email(owner_email)) response = sg.client.mail.send.post(request_body=mail.get()) print(response.status_code) print(response.body) print(response.headers) # ###################################################### # Change Status of 'PENDING' Removal in Audit Table # ###################################################### # Create AuditLog Instance AuditLogInst = DataEventLogger(RemovalQueue_entry.shop_url) # Update Status AuditLogInst.update_status(RemovalQueue_entry.customer_id) # ####################################################### # Log Data Removal in Audit Table (All times are in UTC) # ####################################################### customer_id = customer.id marketing_consent = customer.accepts_marketing action = 'Data Removal Request Processed' method = 'Removal Request Transmitted to Processor' comment = 'Charge-back period terminated, data removal request sent to the Data Processor (Shopfy) via email ([email protected]) for erasure.' referringpage = '-' status = 'Completed' if marketing_consent is True: legal_basis = 'Preparing or Performing a Contract (Product Sales) and Consent (Granted for Marketing)' purpose = 'Ecommerce - Processing Customer Purchases and Marketing' else: legal_basis = 'Preparing or Performing a Contract (Product Sales)' purpose = 'Ecommerce - Processing Customer Purchases' # Add Data Removal Entry line AuditLogInst.insert_data_processing_event(current_datetime, action, method, referringpage, comment,\ customer_id, purpose, status, legal_basis) # ############################################################## # Remove the customer's removal request from the removal queue # ############################################################### RemovalQueue.objects.filter( customer_id=RemovalQueue_entry.customer_id, shop_url=RemovalQueue_entry.shop_url).delete() else: print "Somethong went wrong in gdpr.tasks.removal_queue_processing" print "Removal Queue Processing Terminated SUCCESSFULLY" return 0
def newly_regestered_data(): ##################################################### # New Data Collected (insert multiple rows @ once) #################################################### # Get a list of all shops ShopDeatzInst = ShopDeatz.objects.all() # Extract individual objects from object list, to get their urls and create a store session to send API calls for Store in ShopDeatzInst: shop_url = Store.shop_url auth_token = Store.auth_token # Create and activate store session to retrieve cutomer list session = shopify.Session(shop_url, auth_token) shopify.ShopifyResource.activate_session(session) current_datetime = datetime.datetime.now() # This cron runs at 1am and gets a list of customers who's details were updated between last_import_datetime and current_datetime start_datetime = Store.last_import_datetime.strftime( "%Y-%m-%dT%H:%M:%S") + '-00:00' end_datetime = current_datetime.strftime( "%Y-%m-%dT%H:%M:%S" ) + '-00:00' # format: 2014-04-25T16:15:47-04:00 print start_datetime print end_datetime # Get Customer list # .find() works here because the updated_at_min param access the top-level endoint /admin/customers.json NOT /admin/customers/search.json customer_list = shopify.Customer.find( updated_at_min=start_datetime, updated_at_max=end_datetime ) # Find with no params gets all (Returns a list) # Create AuditLog instance to insert customers AuditLogInst = DataEventLogger(shop_url) # Concatenate customers into multiple VALUE clauses to minimize insertion time and cpu load sql_query_values = '' total_counter = 0 set_counter = 0 for customer in customer_list: total_counter += 1 set_counter += 1 if (customer.id != '') and (customer.id != None): customer_id = customer.id else: customer_id = str(customer.id) # In Case this is an intial data creation if customer.created_at == customer.updated_at: # Convert ISO 8601 string date and time to python datetime object creation_datetime = dateutil.parser.parse(customer.created_at) # Convert datetime object back to UTC time (in case there is a timezon offset) date = creation_datetime.astimezone(pytz.utc) action = 'Data Collection' method = 'Data Collection Form' referringpage = '' comment = 'Personal details provided by data subject were collected and stored in the customer database.' customer_id = customer.id marketing_consent = customer.accepts_marketing status = 'Completed' if marketing_consent is True: legal_basis = 'Preparing or Performing a Contract (Product Sales) and Consent (Granted for Marketing)' purpose = 'Ecommerce - Processing Customer Purchases and Marketing' else: legal_basis = 'Preparing or Performing a Contract (Product Sales)' purpose = 'Ecommerce - Processing Customer Purchases' sql_query_value_line = "('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s'),"\ % ( date, action, method, referringpage, comment, customer_id, purpose, status, legal_basis ) # Otherwise its a data update else: # Convert ISO 8601 string date and time to python datetime object creation_datetime = dateutil.parser.parse(customer.updated_at) # Convert datetime object back to UTC time (in case there is a timezon offset) date = creation_datetime.astimezone(pytz.utc) action = 'Data Rectification' method = 'Rectified via user account.' referringpage = '' comment = 'Data updated.' customer_id = customer.id marketing_consent = customer.accepts_marketing status = 'Completed' if marketing_consent is True: legal_basis = 'Preparing or Performing a Contract (Product Sales) and Consent (Granted for Marketing)' purpose = 'Ecommerce - Processing Customer Purchases and Marketing' else: legal_basis = 'Preparing or Performing a Contract (Product Sales)' purpose = 'Ecommerce - Processing Customer Purchases' sql_query_value_line = "('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s'),"\ % ( date, action, method, referringpage, comment, customer_id, purpose, status, legal_basis ) sql_query_values = sql_query_values + sql_query_value_line # for every set of 150 values (customer inserts) call the insert_multiple_data_events() to process these sql inserts # or if we have reached the end of the customer list then just dump all the values you have left in sql_query_values if set_counter == 150 or total_counter >= len(customer_list): # Pop the last comma off the end of the insert string sql_query_values = sql_query_values[:-1] # Call insert_multiple_data_events() to insert the current set of values AuditLogInst.insert_multiple_data_events(sql_query_values) # reset set_counter and sql_query_values set_counter = 0 sql_query_values = '' # Update last_import_datetime for this store Store.last_import_datetime = current_datetime Store.save() return 0
def gdpr_removal(removal_request_email, confirmation_code, shop_url, referringpage): # Check that email corresponds to confirmation code try: RemovalConfirmInst = RemovalConfirmationCodes.objects.get( removal_request_email=removal_request_email) if RemovalConfirmInst.confirmation_code == confirmation_code: print 'Removal confirmation codes match' # Remove the consumed confirmation code o avoid clashes in future deletions RemovalConfirmInst.delete() pass else: print 'Removal confirmation codes do NOT match' return 0 # in case email cant be found in RemovalConfirmationCodes model, then don't return anything just end function execution except ObjectDoesNotExist: print 'No removal confirmation code entry for email %s' % ( removal_request_email) return 0 # ################################################## # Activate your session with shopify store # ################################################## # Get the Shop's 0auth token, that was stored in installer.models.ShopDeatz during installation ShopDeatzInst = ShopDeatz.objects.get(shop_url=shop_url) auth_token = ShopDeatzInst.auth_token # Create a session object using shop_url and 0auth_token session = shopify.Session(shop_url, auth_token) # Activate the Session shopify.ShopifyResource.activate_session(session) # ################################################## # Check for customers added between last customer # check and removal request # ################################################## # Check for new customers that were added between yesterday and request time (now) in the shop where the removal_request_email is registered current_datetime = datetime.datetime.now() # Get a list of customers who's details were updated between last_import_datetime and current_datetime start_datetime = ShopDeatzInst.last_import_datetime.strftime( "%Y-%m-%dT%H:%M:%S") + '-00:00' end_datetime = current_datetime.strftime( "%Y-%m-%dT%H:%M:%S") + '-00:00' # format: 2014-04-25T16:15:47-04:00 # Get Customer list # .find() works here because the updated_at_min param access the top-level endoint /admin/customers.json NOT /admin/customers/search.json customer_list = shopify.Customer.find( updated_at_min=start_datetime, updated_at_max=end_datetime ) # Find with no params gets all (Returns a list) # Create AuditLog instance to insert customers AuditLogInst = DataEventLogger(shop_url) # Concatenate customers into multiple VALUE clauses to minimize insertion time and cpu load sql_query_values = '' total_counter = 0 set_counter = 0 for customer in customer_list: total_counter += 1 set_counter += 1 if (customer.id != '') and (customer.id != None): customer_id = customer.id else: customer_id = str(customer.id) # In Case this is an intial data creation if customer.created_at == customer.updated_at: # Convert ISO 8601 string date and time to python datetime object creation_datetime = dateutil.parser.parse(customer.created_at) # Convert datetime object back to UTC time (in case there is a timezon offset) date = creation_datetime.astimezone(pytz.utc) action = 'Data Collection' method = 'Data Collection Form' referringpage = '' comment = 'Personal details provided by data subject were collected and stored in the customer database.' customer_id = customer.id marketing_consent = customer.accepts_marketing status = 'Completed' if marketing_consent is True: legal_basis = 'Preparing or Performing a Contract (Product Sales) and Consent (Granted for Marketing)' purpose = 'Ecommerce - Processing Customer Purchases and Marketing' else: legal_basis = 'Preparing or Performing a Contract (Product Sales)' purpose = 'Ecommerce - Processing Customer Purchases' sql_query_value_line = "('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s'),"\ % ( date, action, method, referringpage, comment, customer_id, purpose, status, legal_basis ) # Otherwise its a data update else: # Convert ISO 8601 string date and time to python datetime object creation_datetime = dateutil.parser.parse(customer.updated_at) # Convert datetime object back to UTC time (in case there is a timezon offset) date = creation_datetime.astimezone(pytz.utc) action = 'Data Rectification' method = 'User Account' referringpage = '' comment = 'Data subject rectified personal details, subject records were updated to reflect changes.' customer_id = customer.id marketing_consent = customer.accepts_marketing status = 'Completed' if marketing_consent is True: legal_basis = 'Preparing or Performing a Contract (Product Sales) and Consent (Granted for Marketing)' purpose = 'Ecommerce - Processing Customer Purchases and Marketing' else: legal_basis = 'Preparing or Performing a Contract (Product Sales)' purpose = 'Ecommerce - Processing Customer Purchases' sql_query_value_line = "('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s'),"\ % ( date, action, method, referringpage, comment, customer_id, purpose, status, legal_basis ) sql_query_values = sql_query_values + sql_query_value_line # for every set of 150 values (customer inserts) call the insert_multiple_data_events() to process these sql inserts # or if we have reached the end of the customer list then just dump all the values you have left in sql_query_values if set_counter == 150 or total_counter >= len(customer_list): # Pop the last comma off the end of the insert string sql_query_values = sql_query_values[:-1] # Call insert_multiple_data_events() to insert the current set of values AuditLogInst.insert_multiple_data_events(sql_query_values) # reset set_counter and sql_query_values set_counter = 0 sql_query_values = '' # Update last_import_datetime ShopDeatzInst.last_import_datetime = current_datetime ShopDeatzInst.save() # ##################################################### # Make your API CALLS to remove data from shopify store # ##################################################### # First use email to locate the correct customer to remove (get his customer id from shopify store DB) # Note : .find() will not work on Customers for SEARCHING using params, # Search queries are only available on the Customer resource as a separate .search() method # This is because to search, you need to access a different endpont not /admin/customers.json but rather /admin/customers/search.json search_query = 'email:' + str(removal_request_email) customer_list = shopify.Customer.search( q=search_query) # This should return a list of customers # Now verify that the list of customers is greater than 0, meaning we found the customer # then update the attributes of the customer and save if len(customer_list) > 0: pass else: print 'No matiching customers found in DB' return 0 # Go through the list of customers and check if they have an order history for customer in customer_list: if (customer.last_order_id == None) and (customer.orders_count == 0): # If no history then delete the customer from the using the API customer_id = customer.id marketing_consent = customer.accepts_marketing customer.destroy() #destroy replaces the REST-ful DELETE request # ####################################################### # Log Data Removal in Audit Table (All times are in UTC) # ####################################################### action = 'Data Removal Request Processed' method = 'Data Removed via Shopify API' comment = 'Data removal confirmed via email removal link. Data subject personal data removed from store database via Shopify API.' status = 'Completed' if marketing_consent is True: legal_basis = 'Preparing or Performing a Contract (Product Sales) and Consent (Granted for Marketing)' purpose = 'Ecommerce - Processing Customer Purchases and Marketing' else: legal_basis = 'Preparing or Performing a Contract (Product Sales)' purpose = 'Ecommerce - Processing Customer Purchases' # Create AuditLog Instance AuditLogInst = DataEventLogger(shop_url) # Add Data Removal Entry line AuditLogInst.insert_data_processing_event(current_datetime, action, method, referringpage, comment,\ customer_id, purpose, status, legal_basis) # Update Data Copy and Data Registration lines to remove email and replace with Removal_Dataevent_ID # AuditLogInst.update_event_id(dataevent_id, removal_request_email) else: # Queue the order in gdpr.models.RemovalQueue for processing by celery task below last_order = shopify.Order.find(customer.last_order_id) last_order_date = dateutil.parser.parse(last_order.created_at) RemovalQueueInst = RemovalQueue() RemovalQueueInst.shop_url = shop_url RemovalQueueInst.customer_id = customer.id RemovalQueueInst.last_order_id = customer.last_order_id RemovalQueueInst.last_order_date = last_order_date RemovalQueueInst.removal_request_date = current_datetime RemovalQueueInst.removal_request_email = removal_request_email RemovalQueueInst.save() # ######################################################## # Log Queued Removal in Audit Table (All times are in UTC) # ######################################################## customer_id = customer.id marketing_consent = customer.accepts_marketing action = 'Data Removal Request Queued' method = 'Removal Request Pending' comment = 'Data removal confirmed via email removal link. Subject data removal request is pending end of charge-back period.' status = 'Pending' if marketing_consent is True: legal_basis = 'Preparing or Performing a Contract (Product Sales) and Consent (Granted for Marketing)' purpose = 'Ecommerce - Processing Customer Purchases and Marketing' else: legal_basis = 'Preparing or Performing a Contract (Product Sales)' purpose = 'Ecommerce - Processing Customer Purchases' # Create AuditLog Instance AuditLogInst = DataEventLogger(shop_url) # Add Data Removal Entry line AuditLogInst.insert_data_processing_event(current_datetime, action, method, referringpage, comment,\ customer_id, purpose, status, legal_basis) print "FINISHED SHOPIFY ERASURE QUEUEING" # NOW START CUSTOMER REMOVAL FROM ESP # ######################################################### # Make your API CALLS to remove data from ESPs # ######################################################### # Check if they are using an ESP if yes get the emailing_service, esp_username, esp_API_key, esp_list_id # Make an object of the MarketingServices() details, by looking up the corresponding ShopDeatz.consent_form_id (foreign key) # Basically get the row corresponding to ShopDeatz.consent_form_id (foreign key), make it into an objects with 'row fields' as attributes ESPInst = ESPCredentials.objects.get(shop_url=shop_url) if ESPInst.configured_ESP == '-': pass # If its blank this means that no service was set in admin dashboard, then dont do anything elif ESPInst.configured_ESP == 'mailchimp': # #Step1: Get API Login Credentials # api_key = ESPInst.esp_API_key list_endpoint = ESPInst.esp_api_endpoint_url + '/3.0/lists' search_endpoint = ESPInst.esp_api_endpoint_url + '/3.0/search-members' # # Step2: Make API call to get list of subscriber lists # # Pass the the 'data' dict to access_token_uri using a POST request response = requests.get( list_endpoint, auth=('MyAPP', api_key)) #USe any arbitrary username # Grab the JSON dictionary that is returned and extract access_token response_dict = response.json( ) # Use the .json method to convert the {data:data} JSON dictionary returned into a Python dictionary # Get the list of lists, and loop through all lists extracting id numbers list_ids = [] if len(response_dict['lists']) > 0: for email_list in response_dict['lists']: list_ids.append(email_list['id']) # Means that there are no lists available on this account else: return 0 # #Step3: Loop through all the lists looking fo user-email # lists_to_erase = [] for id_num in list_ids: params = (('query', removal_request_email), ('list_id', id_num)) response = requests.get(search_endpoint, params=params, auth=('MYAPP', api_key)) response_dict = response.json( ) # Use the .json method to convert the {data:data} JSON dictionary returned into a Python dictionary # if it finds exact matches in this list, then add this list id to lists_to_erase[] if (len(response_dict["exact_matches"]["members"]) > 0): lists_to_erase.append(id_num) # #Step4: Remove useremail from all lists where it was located # for id_num in lists_to_erase: lowercase_email = removal_request_email.lower() email_bytes = lowercase_email.encode( 'utf-8' ) # must convert string to bytes first before hashing email_hash_object = hashlib.md5( email_bytes) # create hash object from byte string email_hash = email_hash_object.hexdigest() # Construct Removal Endpoint URL removal_endpoint = ESPInst.esp_api_endpoint_url + '/3.0/lists/' + id_num + '/members/' + email_hash # Send the Delete request response = requests.delete(removal_endpoint, auth=('MYAPP', api_key)) print "FINISHED MAILCHIMP ERASURE" # ####################################################### # Log MAIL LIST Removal in Audit Table (All times are in UTC) # ####################################################### action = 'Data Removal Request Processed' method = 'Data Removed via MailChimp API' comment = 'Data removal confirmed via email removal link. Subject personal data removed from mailing list via MailChimp API.' status = 'Completed' customer_id = customer.id marketing_consent = customer.accepts_marketing if marketing_consent is True: legal_basis = 'Preparing or Performing a Contract (Product Sales) and Consent (Granted for Marketing)' purpose = 'Ecommerce - Processing Customer Purchases and Marketing' else: legal_basis = 'Preparing or Performing a Contract (Product Sales)' purpose = 'Ecommerce - Processing Customer Purchases' # Create AuditLog Instance AuditLogInst = DataEventLogger(shop_url) # Add Data Removal Entry line AuditLogInst.insert_data_processing_event(current_datetime, action, method, referringpage, comment,\ customer_id, purpose, status, legal_basis) elif ESPInst.configured_ESP == 'omnisend': # Not possible even updtating data leaves traces of old data # Need to wait for omnisend to open up their DELETE portion of the API (coming soon) pass # ####################################################### # Send Email to Partners to request customer data removal # ####################################################### return 0
def initial_data_import(shop_url, auth_token): ######################################################## # Initial Customer Import (insert multiple rows @ once) ######################################################### # Create and activate store session to retrieve cutomer list session = shopify.Session(shop_url, auth_token) shopify.ShopifyResource.activate_session(session) # Get Customer list customer_list = shopify.Customer.find( ) # Find with no params gets all (Returns a list) # Create AuditLog instance to insert customers AuditLogInst = DataEventLogger(shop_url) print AuditLogInst.__dict__ # Concatenate customers into multiple VALUE clauses to minimize insertion time and cpu load sql_query_values = '' total_counter = 0 set_counter = 0 for customer in customer_list: total_counter += 1 set_counter += 1 if (customer.id != '') and (customer.id != None): customer_id = customer.id else: customer_id = str(customer.id) # Convert ISO 8601 string date and time to python datetime object creation_datetime = dateutil.parser.parse(customer.created_at) # Convert datetime object back to UTC time (in case there is a timezon offset) date = creation_datetime.astimezone(pytz.utc) action = 'Data Collection' method = 'Initial Data Import' referringpage = 'Data imported on GDPR App install' comment = 'Personal details provided by data subject were collected and stored in the customer database.' customer_id = customer.id marketing_consent = customer.accepts_marketing status = 'Completed' if marketing_consent is True: legal_basis = 'Preparing or Performing a Contract (Product Sales) and Consent (Granted for Marketing)' purpose = 'Ecommerce - Processing Customer Purchases and Marketing' else: legal_basis = 'Preparing or Performing a Contract (Product Sales)' purpose = 'Ecommerce - Processing Customer Purchases' sql_query_value_line = "('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s'),"\ % ( date, action, method, referringpage, comment, customer_id, purpose, status, legal_basis ) sql_query_values = sql_query_values + sql_query_value_line # for every set of 150 values (customer inserts) call the insert_multiple_data_events() to process these sql inserts # or if we have reached the end of the customer list then just dump all the values you have left in sql_query_values if set_counter == 150 or total_counter >= len(customer_list): # Pop the last comma off the end of the insert string sql_query_values = sql_query_values[:-1] # Call insert_multiple_data_events() to insert the current set of values AuditLogInst.insert_multiple_data_events(sql_query_values) # reset set_counter and sql_query_values set_counter = 0 sql_query_values = '' # Change initial_import flag in ShopDeatz to true and change time for last_import_datetime Store = ShopDeatz.objects.get(shop_url=shop_url) Store.initial_import = True Store.last_import_datetime = datetime.datetime.now() Store.save() print "Inserted Initial Import Data to SQL for %s" % (shop_url) return 0