Unproctect range off cell in openpyxl - openpyxl

I want to export a protected file to excel but leave some cells unproctected. Tried various methods but none of the works. Can someone help me out?
ws.protection.sheet = True
for row in range(4, nr + 2):
for col in range(12 ,15):
char = get_column_letter(col)'
ws[char + str(row)].protection.sheet = False
I get the error message: Style objects are immutable and cannot be changed.Reassign the style with a copy

Related

openpyxl: Is it possible to load a workbook (with data_only=False), work on it, save it and re open the saved vile with (data_only= True)?

Basically the title. The thing is that I got an Excel file already (with a lot of formulas) and I have to use it as a template, but I have to copy certain column and paste it in another column.
Since I have to make some graphs in between I need the numeric data of the excel file so my plan is the following:
1.- load the file with data_only = False.
2.- Make the for loops needed to copy and paste info from one worksheet to another.
3.- Save the copied data as another Excel file.
4.- Open the new Excel created file, this time with data_only = True, so I can work with the numeric values.
The problem is that after doing this, it's like after putting data_only on the new created file it doesn't work, because when I made a list that filters NoneType values and strings in a column that have actual numerical values it gives me an empty list.
#I made the following
wb = load_workbook('file_name.xlsx', data_only = True)
S1 = wb['Sheet 1']
S2 = wb['Sheet 2']
#Determination of min and max cols and rows
col_min = S1.min_column
col_max = S1.max_column
row_min = S1.min_row
row_max = S1.max_row
for i in range(row_min + 2, row_max + 1):
for j in range(col_min + Value, Value + 2):
S2.cell(row = i+6, column = j+10-Value).value = S1.cell(row = i, column = j).value
Transition_file = wb.save('transition.xlsx')
wb1 = load_workbook('transition.xlsx', data_only = True) #To obtain only numerical values
S2 = wb1['Sheet 2'] #Re define my Sheet 2 values

VBA: Where to add source formatting and column width?

I am new in VBA and learning on my own. I was able to build a code that searches for a value (previously define as "Number") on all sheets and for those sheets that have the specific value in an specific cell it will copy a range from the sheet and then paste it in a new worksheet. It is worth saying that I did not use copy and paste (see code below). The code works just fine, but I would like the new sheet to keep the same formatting as the source. I have tried a couple of things, but the stuff that works means I have to use Copy/Paste type coding, which later in the code gives me problems since it changes the ActiveSheet. Other attempts do not work. In addition I would like to have a fix column width for the ranges that are paste into the new sheet.
A piece of the code in question is below. FYI = the variable Number is defined earlier in the code. Also, earlier in the code I create the new sheet.
My Questions:
Foe the code below, is there a way to keep the formatting from the source, without having to use "copy" and "paste" type coding.
To the code below, can I somehow fix the width of the columns where the new staff are going to be paste?
If you have a better, more elegant way to write the code, I would appreciate it.
For k = 1 To wscount - 1
If Worksheets(k).Range("F2").Value = Number Then
j = j + 1
Worksheets(wscount + 1).Range(Cells(1, 1 + j), Cells(100, 1 + j)).Value = Worksheets(k).Range("F1:F100").Value
End If
Next
You can assign property value of the source cell to target cell just like what you do with value property.
Option Explicit
Public Sub CopyValueAndFormat()
For k = 1 To wscount - 1
If Worksheets(k).Range("F2").Value = Number Then
j = j + 1
Worksheets(wscount + 1).Range(Cells(1, 1 + j), Cells(100, 1 + j)).Value = Worksheets(k).Range("F1:F100").Value
'call setFormat method
setFormat Worksheets(k).Range("F1:F100"), Worksheets(wscount + 1).Range(Cells(1, 1 + j), Cells(100, 1 + j))
End If
Next
End Sub
Private Sub setFormat(source As Range, target As Range)
'set target format equal to source format
With target
.Value = source.Value
.Interior.Color = source.Interior.Color
.Font.FontStyle = source.Font.FontStyle
'add others property to format
'call AutoFit method to set column width
.EntireColumn.AutoFit
End With
End Sub

Dynamically add items from a file to a ComboBox

I am working on an application that allows the user to dynamically add to and remove items from an excel file. The quantity of items shall be unlimited.
I am looking for a way to grab the items from the excel file and transfer them to the ComboBox.
To make myself clearer: The problem is not iterating through cells, but getting cell values into the ComboBox. I need a method that captures the content of all cells with values in a given column, where the end of range is unknown and then transfer the values to a ComboBox.
The Combobox only accepts values, not any empty cells. I also don't want fields in the ComboBox that say "No Value".
I have tried itering through cells and range methods, but this doesn't get the values into the ComboBox.
What I have so far is:
wb = load_workbook (source_file)
ws = wb.active
self.value_1 = ws['B2'].value
self.value_2 = ws['B3'].value
self.value_3 = ws['B4'].value
self.value_4 = ws['B5'].value
self.value_5 = ws['B6'].value
self.value_6 = ws['B7'].value
self.value_7 = ws['B8'].value
self.value_8 = ws['B9'].value
self.value_9 = ws['B10'].value
self.value_10 = ws['B11'].value
stock_items = [ self.value_1 , self.value_2 , self.value_3 , self.value_4 , self.value_5 ,
self.value_6 , self.value_7 , self.value_8 , self.value_9 , self.value_10 ]
self.combo_items_list = [ ]
for stock_item in stock_items :
if stock_item != None :
self.combo_items_list.append (stock_item)
self.combo.addItems(self.combo_items_list)
This works as expected, but what troubles me is that I have to add a line of code for each item I grab from the excel file, besides having to put an extra entry into the stock_items list. If there were 5.000 items in the file, that would result in 5.000 lines of code and 5000 entries in the list.
Is there a more efficient and elegant way to handle the issue with "counter" or pandas?
Thanks in advance.
I found a way to do this nicely using Pandas, not opnpyxl :
import pandas as pd
import numpy as np
# get sheet and whole column
sales = pd.read_excel ("Inventory.xlsx")
# filter out any None Values
sales_article = sales ["Artigo"] .dropna()
# transform into list
sales_list = sales_article.values.tolist()
# add list to ComboBox
self.combo.addItems(sales_list)
In openpyxl 2.4 worksheets have an iter_cols method that allow you to select a range of cells and have them returned as columns. Just like iter_rows returns them as rows. This is the simplest and most efficient way to do what you want to do.
See https://openpyxl.readthedocs.io/en/default/tutorial.html#accessing-many-cells for details.
An example for your use case:
cells = [cell.value for cell in ws.iter_cols(min_col=2, max_col=2, min_row=2) if cell.value is not None]
wb = load_workbook(source_file)
ws = wb.active
lastrow = ws.UsedRange.Height # don't remember method name
for row in range(lastrow):
value = ws['B' + str(row + 2)].value
if value is not None:
self.combo_items_list.append (value)
self.combo.addItems(self.combo_items_list)
See worksheet docs for other ways to get range of excel rows.
wb = load_workbook (source_file)
ws = wb.active
self.combo_items_list = [ ]
// loop from 2(start)-11(end)
// check if ws['B'<counter>].value is available and not null
// add this value to your array of combo for each ittration.
self.combo_items_list.append (ws['B'<counter>].value)
self.combo.addItems(self.combo_items_list)
Sorry, if i am getting it wrong. I don't know the syntax of given language. Still saw a logical answer and decided to post.

VBA Excel, assign formula to row in range

I am new to VBA and wanted to ask if you could help me.
I have this VB6 code:
For i = 2 To rowCount - 2
' Fill Ji with a formula(=MID(Fi,11,9)) and apply format.
Set oRng = oSheet.Range(Cells(i, 10), Cells(rowCount - 2, 10))
**oRng.formula = "=MID(Cells(i,6),11,9)"**
oRng.NumberFormat = "[$-F400]hh:mm:ss"
Next i
I want to assign a formula to the range
Tried this code but, has a problem when I assign the formula. It doesn't recognizes the Cell(i,6) as Cell, but As string "Cell(i,6)".
Can anyone help me?
You don't want the loop as you're putting the formula into all cells at once:
' Fill Ji with a formula(=MID(Fi,11,9)) and apply format.
Set oRng = oSheet.Range(oSheet.Cells(2, 10), oSheet.Cells(rowCount - 2, 10))
oRng.formulaR1C1 = "=MID(RC6,11,9)+0"
oRng.NumberFormat = "[$-F400]hh:mm:ss"
Note: I added a +0 to your formula to convert text to true time values.
Try "=MID(" & Cells(i,6).Address(False,False) &",11,9)"
Include the Address part only if you want a relative reference

Porting VBA To IronPython

I am trying to translate the VBA code found in this link into IronPython. Can anyone recommend a good VBA resource to explain how best to do this for a Python programmer?
I have all the Excel portions implemented, such as the treatment and use of objects, workbooks, worksheets, etc.
I will also settle for an explanation of this snippet of code:
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Put the index value of the sheet into Arr. Ensure there
' are no duplicates. If Arr(N) is not zero, we've already
' loaded that element of Arr and thus have duplicate sheet
' names.
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
If Arr(N) > 0 Then
ErrorText = "Duplicate worksheet name in NameArray."
SortWorksheetsByNameArray = False
Exit Function
End If
why would Arr(N) ever NOT be greater than 0?
Here is my current code, which is broken:
def move_worksheets_according_to_list(self, name_list):
wb = self.com_workbook
temp_list = []
for n in range(len(name_list)):
if name_list.count(name_list[n]) > 1:
raise Exception("Duplicate worksheet name in NameArray.")
else:
temp_list.append(wb.Worksheets(name_list[n]).Index)
for m in range(len(temp_list)):
for n in range(m, len(temp_list)):
if temp_list[n] < temp_list[m]:
l = temp_list[n]
temp_list[n] = temp_list[m]
temp_list[m] = l
if not all(temp_list[i] <= temp_list[i+1] for i in xrange(len(temp_list)-1)):
return False
print "current order"
for sheet in wb.Worksheets:
print sheet.Name
wb.Worksheets(name_list[0]).Move(Before=wb.Worksheets(1))
#WB.Worksheets(NameArray(LBound(NameArray))).Move before:=WB.Worksheets(Arr(1))
for n in range(len(name_list)-1):
print 'moving ', name_list[n], 'before ', name_list[n+1]
wb.Worksheets(name_list[n]).Move(Before=wb.Worksheets(name_list[n + 1]))
Note:
With this answer as reference, here is all I had to do:
def move_worksheets_according_to_list(self, name_list):
wb = self.com_workbook
l = []
# since wb.Worksheets is a com_object, you can't use the "if _ in XXX"
# construct without converting to a list first
for s in wb.Worksheets:
l.append(s.Name)
for n in range(len(name_list)):
if name_list[n] in l:
wb.Worksheets(name_list[n]).Move(After=wb.Worksheets(wb.Worksheets.Count))
Declaring and dimensioning an array of longs in VBA will create the array with the default value of 0 in each slot.
def move_worksheets_according_to_list(self, name_list):
wb = self.com_workbook
l = []
# since wb.Worksheets is a com_object, you can't use the "if _ in XXX"
# construct without converting to a list first
for s in wb.Worksheets:
l.append(s.Name)
for n in range(len(name_list)):
if name_list[n] in l:
wb.Worksheets(name_list[n]).Move(After=wb.Worksheets(wb.Worksheets.Count)